// This is a part of the Active Template Library.
// Copyright (C) Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Active Template Library Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Active Template Library product.

#ifndef __ATLDB_H__
#define __ATLDB_H__

#pragma once

#ifndef _ATL_NO_PRAGMA_WARNINGS
#pragma warning (push)
#pragma warning(disable: 4702) // unreachable code
#pragma warning(disable: 4984) // if constexpr
#endif //!_ATL_NO_PRAGMA_WARNINGS

// OLE DB Provider Support

// Interface Impl Classes
//
// Data Source Object
//
// -Mandatory Interfaces:
//	IDBCreateSession
//	IDBInitialize
//	IDBProperties
//	IPersist
//
// Session Object
//
// -Mandatory Interfaces:
//	IGetDataSource
//	IOpenRowset
//	ISessionProperties
//
// -Optional Interfaces:
//	IDBCreateCommand
//	IDBSchemaRowset
//
// Rowset Object
//
// -Mandatory Interfaces:
//	IAccessor
//	IColumnsInfo
//	IConvertType
//	IRowset
//	IRowsetInfo
//
// -Optional Interfaces:
//	IRowsetIdentity
//
// Command Object
//
// -Mandatory Interfaces:
// ICommand)
// IAccessor)
// ICommandProperties
// ICommandText - derives from ICommand
// IColumnsInfo
// IConvertType

#include <atldef.h>

#if !defined(_ATL_USE_WINAPI_FAMILY_DESKTOP_APP)
#error This file is not compatible with the current WINAPI_FAMILY
#endif

#include <oledb.h>
#include <limits.h>
#include <oledberr.h>
#include <msdadc.h>
#include <atldbcli.h>
#include <atlcoll.h>

#pragma warning(disable: 4244)
#pragma warning(disable: 4984)  // if constexpr


#pragma pack(push,_ATL_PACKING)
namespace ATL
{

inline DBROWCOUNT AbsVal(_In_ DBROWCOUNT val)
{
	if( val < 0 )
		return -val;
	else
		return val;
}

/* Function detection for Fire_OnFieldChange. Signature is:

HRESULT Fire_OnFieldChange(
	_Inout_ IRowset* pRowset,
	_In_ HROW hRow,
	_In_ DBORDINAL cColumns,
	_In_reads_(cColumns) DBORDINAL* rgColumns,
	_In_ DBREASON eReason,
	_In_ DBEVENTPHASE ePhase,
	_In_ BOOL fCantDeny)
*/
template<class T, class = void>
struct Has_Fire_OnFieldChange : ::std::false_type {};

template<class T>
struct Has_Fire_OnFieldChange<T, ::std::void_t<
	decltype(::std::declval<T>().Fire_OnFieldChange(
		::std::declval<IRowset*>(),
		HROW{},
		DBORDINAL{},
		::std::declval<DBORDINAL*>(),
		DBREASON{},
		DBEVENTPHASE{},
		BOOL{}))>> : ::std::true_type {};

#ifndef _ATL_IF_EXISTS_FALLBACK
#define _ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE if constexpr(Has_Fire_OnFieldChange<T>::value)
#else
#define _ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE __if_exists(T::Fire_OnFieldChange)
#endif

/* Function detection for Fire_OnRowChange. Signature is:

HRESULT Fire_OnRowChange(
	_Inout_ IRowset* pRowset,
	_In_ DBCOUNTITEM cRows,
	_In_reads_(cRows) const HROW rghRows[],
	_In_ DBREASON eReason,
	_In_ DBEVENTPHASE ePhase,
	_In_ BOOL fCantDeny)
*/
template<class T, class = void>
struct Has_Fire_OnRowChange : ::std::false_type {};

template<class T>
struct Has_Fire_OnRowChange<T, ::std::void_t<
	decltype(::std::declval<T>().Fire_OnRowChange(
		::std::declval<IRowset*>(),
		DBCOUNTITEM{},
		::std::declval<HROW*>(),
		DBREASON{},
		DBEVENTPHASE{},
		BOOL{}))>> : ::std::true_type {};

#ifndef _ATL_IF_EXISTS_FALLBACK
#define _ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE if constexpr(Has_Fire_OnRowChange<T>::value)
#else
#define _ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE __if_exists(T::Fire_OnRowChange)
#endif

/* Function detection for Fire_OnRowsetChange. Signature is:

HRESULT Fire_OnRowsetChange(
	_Inout_ IRowset* pRowset,
	_In_ DBREASON eReason,
	_In_ DBEVENTPHASE ePhase,
	_In_ BOOL fCantDeny)
*/
template<class T, class = void>
struct Has_Fire_OnRowsetChange : ::std::false_type {};

template<class T>
struct Has_Fire_OnRowsetChange<T, ::std::void_t<
	decltype(::std::declval<T>().Fire_OnRowsetChange(
		::std::declval<IRowset*>(),
		DBREASON{},
		DBEVENTPHASE{},
		BOOL{})) >> : ::std::true_type {};

#ifndef _ATL_IF_EXISTS_FALLBACK
#define _ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE if constexpr(Has_Fire_OnRowsetChange<T>::value)
#else
#define _ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE __if_exists(T::Fire_OnRowsetChange)
#endif

///////////////////////////////////////////////////////////////////////////
// Forwards
template <class T> class CUtlPropInfo;
class CColumnIds;

///////////////////////////////////////////////////////////////////////////
// Additional Property Flag needed internally
const int	DBPROPFLAGS_CHANGE	= 0x40000000;

///////////////////////////////////////////////////////////////////////////
// ATL Provider Property Definitions
#define ATLDB_NO_STRING		0x01000011	// Arbitrary value for AtlDumpProperty

///////////////////////////////////////////////////////////////////////////
// ATL Provider Property Debugging Support

inline void WINAPI AtlDumpPropsetIID(
	_In_ REFIID iid,
	_In_ DWORD dwStatus)
{
	USES_CONVERSION_EX;
	// Handle the most common ones
	TCHAR szPropertySetName[100];

	if(InlineIsEqualGUID(iid, DBPROPSET_DATASOURCEALL))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_DATASOURCEALL -"));
	}
	else if(InlineIsEqualGUID(iid, DBPROPSET_DATASOURCEINFOALL))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_DATASOURCEINFOALL -"));
	}
	else if(InlineIsEqualGUID(iid, DBPROPSET_ROWSETALL))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_ROWSETALL -"));
	}
	else if(InlineIsEqualGUID(iid,DBPROPSET_DBINITALL))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_DBINITALL -"));
	}
	else if(InlineIsEqualGUID(iid,DBPROPSET_SESSIONALL))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_SESSIONALL -"));
	}
	if(InlineIsEqualGUID(iid, DBPROPSET_DATASOURCE))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_DATASOURCE -"));
	}
	else if(InlineIsEqualGUID(iid, DBPROPSET_DATASOURCEINFO))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_DATASOURCEINFO -"));
	}
	else if(InlineIsEqualGUID(iid, DBPROPSET_ROWSET))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_ROWSET -"));
	}
	else if(InlineIsEqualGUID(iid,DBPROPSET_DBINIT))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_DBINIT -"));
	}
	else if(InlineIsEqualGUID(iid,DBPROPSET_SESSION))
	{
		Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName),_T("DBPROPSET_SESSION -"));
	}
	else
	{
		LPOLESTR lpszName = NULL;
		if (SUCCEEDED(StringFromCLSID(iid, &lpszName)))
		{
			Checked::tcscpy_s(szPropertySetName,
							  _countof(szPropertySetName),
							  SAL_Assume_notnull_for_opt_z_(OLE2T_EX(lpszName, _ATL_SAFE_ALLOCA_DEF_THRESHOLD)));
		}
		else
		{
			Checked::tcscpy_s(szPropertySetName, _countof(szPropertySetName), _T("Unknown DBPROPSET -"));
		}
	}

	if (dwStatus & 0x04 /* GETPROP_ERRORSOCCURRED */)
	{
		Checked::tcscat_s(szPropertySetName, _countof(szPropertySetName), _T(" NOT FOUND\n"));
	}
	else
	{
		Checked::tcscat_s(szPropertySetName, _countof(szPropertySetName), _T(" FOUND\n"));
	}

	OutputDebugString(szPropertySetName);
}


inline void WINAPI AtlDumpProperty(
	_In_ DWORD dwPropertyID,
	_In_ DWORD dwStatus)
{
	TCHAR szProperty[100];
	TCHAR szStatus[24];

	switch(dwStatus)
	{
	case DBPROPSTATUS_OK:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("FOUND"));
		break;
	case DBPROPSTATUS_NOTSUPPORTED:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("NOT SUPPORTED"));
		break;
	case DBPROPSTATUS_BADVALUE:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("BAD VALUE"));
		break;
	case DBPROPSTATUS_BADOPTION:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("BAD OPTION"));
		break;
	case DBPROPSTATUS_BADCOLUMN:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("BAD COLUMN"));
		break;
	case DBPROPSTATUS_NOTALLSETTABLE:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("NOT ALL SETTABLE"));
		break;
	case DBPROPSTATUS_NOTSETTABLE:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("NOT SETTABLE"));
		break;
	case DBPROPSTATUS_NOTSET:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("NOT SET"));
		break;
	case DBPROPSTATUS_CONFLICTING:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("CONFLICTED"));
		break;
	case ATLDB_NO_STRING:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("WARNING!  NO RESOURCE STRING FOR THIS PROPERTY!  ADD IDS_<PROP_NAME>"));
		break;
	default:
		Checked::tcscpy_s(szStatus, _countof(szStatus), _T("INDETERMINATE"));
		break;
	}

#pragma warning(push)
#pragma warning(disable: 6271)
	_stprintf_s(szProperty, _countof(szProperty), _T("Property 0x%lxL -- %Ts\n"), dwPropertyID, szStatus);
#pragma warning(pop)

	OutputDebugString(szProperty);
}


///////////////////////////////////////////////////////////////////////////
// Defines for debugging properties

#ifdef _ATL_DEBUG_PROVIDER_PROPS
#define _ATLDUMPPROPSETIID(iid, dwStatus)	AtlDumpPropsetIID(iid, dwStatus)
#define _ATLDUMPPROPERTY(dwPropertyID, dwStatus) AtlDumpProperty(dwPropertyID, dwStatus)
#else
#define _ATLDUMPPROPSETIID(iid, dwStatus)
#define _ATLDUMPPROPERTY(dwPropertyID, dwStatus)
#endif // _ATL_DEBUG_PROVIDER_PROPS

// -------------  STRUCTURE DEFINITIONS -----------------------------------

struct UPROPVAL
{
	DBPROPOPTIONS	dwOption;
	CColumnIds*		pCColumnIds;
	DWORD			dwFlags;
	VARIANT			vValue;
};

struct UPROPINFO
{
	DBPROPID	dwPropId;
	ULONG		ulIDS;
	VARTYPE		VarType;
	DBPROPFLAGS	dwFlags;
	union
	{
		DWORD_PTR dwVal;
		LPOLESTR szVal;
	};
	DBPROPOPTIONS dwOption;
};

struct UPROP
{
	ULONG			cPropIds;
	UPROPINFO**		rgpUPropInfo;
	UPROPVAL*		pUPropVal;
};

struct PROPCOLID
{
	DBID			dbidProperty;	// The column id information
	DBPROPOPTIONS	dwOption;
	VARIANT			vValue;
};

typedef PROPCOLID* PPROPCOLID;

struct UPROPSET
{
	const GUID* pPropSet;
	ULONG cUPropInfo;
	UPROPINFO* pUPropInfo;
	DWORD dwFlags;
	bool bIsChained;
};

struct ATLBINDINGS
{
	DBBINDING* pBindings;
	DWORD dwRef;//DBREFCOUNT dwRef;
	DBCOUNTITEM cBindings;
	DBACCESSORFLAGS dwAccessorFlags;
};

struct ATLCOLUMNINFO
{
	LPOLESTR pwszName;
	ITypeInfo *pTypeInfo;
	DBORDINAL iOrdinal;
	DBCOLUMNFLAGS dwFlags;
	DBLENGTH ulColumnSize;
	DBTYPE wType;
	BYTE bPrecision;
	BYTE bScale;
	DBID columnid;
	DBBYTEOFFSET cbOffset;
};

//
// The following very large sections of defines are to implement auto determination
// of Property map constants based on a stringized prop name.  There is one set for
// Type (VT_), one for Init Value, and one for Property flags.
//

#define ABORTPRESERVE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define ACTIVESESSIONS_Flags  ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define APPENDONLY_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define ASYNCTXNABORT_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define ASYNCTXNCOMMIT_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define AUTH_CACHE_AUTHINFO_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define AUTH_ENCRYPT_PASSWORD_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define AUTH_INTEGRATED_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define AUTH_MASK_PASSWORD_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define AUTH_PASSWORD_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define AUTH_PERSIST_ENCRYPTED_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define AUTH_PERSIST_SENSITIVE_AUTHINFO_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define AUTH_USERID_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define BLOCKINGSTORAGEOBJECTS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define BOOKMARKS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define BOOKMARKSKIPPED_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define BOOKMARKTYPE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define BYREFACCESSORS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define CACHEDEFERRED_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define CANFETCHBACKWARDS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define CANHOLDROWS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define CANSCROLLBACKWARDS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define CATALOGLOCATION_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define CATALOGTERM_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define CATALOGUSAGE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define CHANGEINSERTEDROWS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_CHANGE )
#define COL_AUTOINCREMENT_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COL_DEFAULT_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COL_DESCRIPTION_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COL_FIXEDLENGTH_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COL_NULLABLE_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COL_PRIMARYKEY_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COL_UNIQUE_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COLUMNDEFINITION_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define COLUMNRESTRICT_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define COMMANDTIMEOUT_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COMMITPRESERVE_Flags ( DBPROPFLAGS_ROWSET| DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define CONCATNULLBEHAVIOR_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define CURRENTCATALOG_Flags ( DBPROPFLAGS_DATASOURCE | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define DATASOURCENAME_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define DATASOURCEREADONLY_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define DBMSNAME_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define DBMSVER_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define DEFERRED_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define DELAYSTORAGEOBJECTS_Flags  ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define DSOTHREADMODEL_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define GROUPBY_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define HETEROGENEOUSTABLES_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define IAccessor_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IColumnsInfo_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IColumnsRowset_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IConnectionPointContainer_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define IConvertType_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IRowset_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IRowsetChange_Flags  ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IRowsetIdentity_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IRowsetIndex_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IRowsetInfo_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IRowsetLocate_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define IRowsetResynch_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IRowsetScroll_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define IRowsetUpdate_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define ISupportErrorInfo_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define ILockBytes_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define ISequentialStream_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IStorage_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IStream_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IDENTIFIERCASE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define IMMOBILEROWS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_AUTOUPDATE_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_CLUSTERED_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_FILLFACTOR_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_INITIALSIZE_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_NULLCOLLATION_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_NULLS_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_PRIMARYKEY_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_SORTBOOKMARKS_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_TEMPINDEX_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_TYPE_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INDEX_UNIQUE_Flags ( DBPROPFLAGS_INDEX | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_DATASOURCE_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_HWND_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_IMPERSONATION_LEVEL_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_LCID_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_LOCATION_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_MODE_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_PROMPT_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_PROTECTION_LEVEL_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_PROVIDERSTRING_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_TIMEOUT_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define LITERALBOOKMARKS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define LITERALIDENTITY_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MAXINDEXSIZE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MAXOPENROWS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define MAXPENDINGROWS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define MAXROWS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define MAXROWSIZE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MAXROWSIZEINCLUDESBLOB_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MAXTABLESINSELECT_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MAYWRITECOLUMN_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define MEMORYUSAGE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define MULTIPLEPARAMSETS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MULTIPLERESULTS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MULTIPLESTORAGEOBJECTS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MULTITABLEUPDATE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define NOTIFICATIONGRANULARITY_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define NOTIFICATIONPHASES_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYCOLUMNSET_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWDELETE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWFIRSTCHANGE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWINSERT_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWRESYNCH_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWSETRELEASE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWSETFETCHPOSITIONCHANGE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWUNDOCHANGE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWUNDODELETE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWUNDOINSERT_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NOTIFYROWUPDATE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define NULLCOLLATION_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define OLEOBJECTS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define ORDERBYCOLUMNSINSELECT_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define ORDEREDBOOKMARKS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define OTHERINSERT_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define OTHERUPDATEDELETE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define OUTPUTPARAMETERAVAILABILITY_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define OWNINSERT_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define OWNUPDATEDELETE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define PERSISTENTIDTYPE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define PREPAREABORTBEHAVIOR_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define PREPARECOMMITBEHAVIOR_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define PROCEDURETERM_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define PROVIDERNAME_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define PROVIDEROLEDBVER_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define PROVIDERVER_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define QUICKRESTART_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define QUOTEDIDENTIFIERCASE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define REENTRANTEVENTS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define REMOVEDELETED_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define REPORTMULTIPLECHANGES_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_CHANGE )
#define RETURNPENDINGINSERTS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define ROWRESTRICT_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define ROWSETCONVERSIONSONCOMMAND_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define ROWTHREADMODEL_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define SCHEMATERM_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define SCHEMAUSAGE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define SERVERCURSOR_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define SESS_AUTOCOMMITISOLEVELS_Flags ( DBPROPFLAGS_SESSION | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE)
#define SQLSUPPORT_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define STRONGIDENTITY_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define STRUCTUREDSTORAGE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define SUBQUERIES_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define SUPPORTEDTXNDDL_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define SUPPORTEDTXNISOLEVELS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define SUPPORTEDTXNISORETAIN_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define TABLETERM_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define TBL_TEMPTABLE_Flags ( DBPROPFLAGS_TABLE | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define TRANSACTEDOBJECT_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define UPDATABILITY_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define USERNAME_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
// 1.5
#define FILTERCOMPAREOPS_Flags ( DBPROPFLAGS_VIEW | DBPROPFLAGS_READ )
#define FINDCOMPAREOPS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define IChapteredRowset_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IDBAsynchStatus_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IRowsetFind_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IRowsetView_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IViewChapter_Flags ( DBPROPFLAGS_VIEW | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IViewFilter_Flags ( DBPROPFLAGS_VIEW | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IViewRowset_Flags ( DBPROPFLAGS_VIEW | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IViewSort_Flags ( DBPROPFLAGS_VIEW | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_ASYNCH_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define MAXOPENCHAPTERS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MAXORSINFILTER_Flags ( DBPROPFLAGS_VIEW | DBPROPFLAGS_READ )
#define MAXSORTCOLUMNS_Flags ( DBPROPFLAGS_VIEW | DBPROPFLAGS_READ )
#define ROWSET_ASYNCH_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define SORTONINDEX_Flags ( DBPROPFLAGS_VIEW | DBPROPFLAGS_READ )
// 2.0
#define IMultipleResults_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define DATASOURCE_TYPE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
//MDPROP
#define AXES_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define FLATTENING_SUPPORT_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_JOINCUBES_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define NAMED_LEVELS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
//#define RANGEROWSET_Flags (  )
#define MDX_SLICER_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
//#define MDX_CUBEQUALIFICATION_Flags (  )
#define MDX_OUTERREFERENCE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_QUERYBYPROPERTY_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_CASESUPPORT_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_STRING_COMPOP_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_DESCFLAGS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_SET_FUNCTIONS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_MEMBER_FUNCTIONS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_NUMERIC_FUNCTIONS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_FORMULAS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define AGGREGATECELL_UPDATE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
//#define MDX_AGGREGATECELL_UPDATE_Flags (  )
#define MDX_OBJQUALIFICATION_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define MDX_NONMEASURE_EXPRESSONS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
// DBPROP
#define ACCESSORDER_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define BOOKMARKINFO_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define INIT_CATALOG_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define ROW_BULKOPS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE ) //!!!
#define PROVIDERFRIENDLYNAME_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define LOCKMODE_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define MULTIPLECONNECTIONS_Flags ( DBPROPFLAGS_DATASOURCE | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define UNIQUEROWS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define SERVERDATAONINSERT_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
//#define STORAGEFLAGS_Flags (  )
#define CONNECTIONSTATUS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define ALTERCOLUMN_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define COLUMNLCID_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ )
#define RESETDATASOURCE_Flags ( DBPROPFLAGS_DATASOURCE | DBPROPFLAGS_WRITE )
#define INIT_OLEDBSERVICES_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IRowsetRefresh_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define SERVERNAME_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define IParentRowset_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define HIDDENCOLUMNS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ )
#define PROVIDERMEMORY_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define CLIENTCURSOR_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
// 2.1
#define TRUSTEE_USERNAME_Flags ( DBPROPFLAGS_TRUSTEE | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define TRUSTEE_AUTHENTICATION_Flags ( DBPROPFLAGS_TRUSTEE | DBPROPFLAGS_WRITE )
#define TRUSTEE_NEWAUTHENTICATION_Flags ( DBPROPFLAGS_TRUSTEE | DBPROPFLAGS_WRITE )
#define IRow_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IRowChange_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IRowSchemaChange_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IGetRow_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IScopedOperations_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IBindResource_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define ICreateRow_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_BINDFLAGS_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_LOCKOWNER_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define GENERATEURL_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
//#define IDBBinderProperties_Flags (  )
#define IColumnsInfo2_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
//#define IRegisterProvider_Flags (  )
#define IGetSession_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IGetSourceRow_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IRowsetCurrentIndex_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define OPENROWSETSUPPORT_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define COL_ISLONG_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
// 2.5
#define COL_SEED_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COL_INCREMENT_Flags ( DBPROPFLAGS_COLUMN | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define INIT_GENERALTIMEOUT_Flags ( DBPROPFLAGS_DBINIT | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define COMSERVICES_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
// 2.6
#define OUTPUTSTREAM_Flags ( DBPROPFLAGS_STREAM | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define OUTPUTENCODING_Flags ( DBPROPFLAGS_STREAM | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define TABLESTATISTICS_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_READ )
#define SKIPROWCOUNTRESULTS_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define IRowsetBookmark_Flags ( DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE )
#define VISUALMODE_Flags ( DBPROPFLAGS_DATASOURCEINFO | DBPROPFLAGS_WRITE )


#define ABORTPRESERVE_Type VT_BOOL
#define ACTIVESESSIONS_Type VT_I4
#define APPENDONLY_Type VT_BOOL
#define ASYNCTXNABORT_Type VT_BOOL
#define ASYNCTXNCOMMIT_Type VT_BOOL
#define AUTH_CACHE_AUTHINFO_Type VT_BOOL
#define AUTH_ENCRYPT_PASSWORD_Type VT_BOOL
#define AUTH_INTEGRATED_Type VT_BSTR
#define AUTH_MASK_PASSWORD_Type VT_BOOL
#define AUTH_PASSWORD_Type VT_BSTR
#define AUTH_PERSIST_ENCRYPTED_Type VT_BOOL
#define AUTH_PERSIST_SENSITIVE_AUTHINFO_Type VT_BOOL
#define AUTH_USERID_Type VT_BSTR
#define BLOCKINGSTORAGEOBJECTS_Type VT_BOOL
#define BOOKMARKS_Type VT_BOOL
#define BOOKMARKSKIPPED_Type VT_BOOL
#define BOOKMARKTYPE_Type VT_I4
#define BYREFACCESSORS_Type VT_BOOL
#define CACHEDEFERRED_Type VT_BOOL
#define CANFETCHBACKWARDS_Type VT_BOOL
#define CANHOLDROWS_Type VT_BOOL
#define CANSCROLLBACKWARDS_Type VT_BOOL
#define CATALOGLOCATION_Type VT_I4
#define CATALOGTERM_Type VT_BSTR
#define CATALOGUSAGE_Type VT_I4
#define CHANGEINSERTEDROWS_Type VT_BOOL
#define COL_AUTOINCREMENT_Type VT_BOOL
#define COL_DEFAULT_Type VT_BSTR
#define COL_DESCRIPTION_Type VT_BSTR
#define COL_FIXEDLENGTH_Type VT_BOOL
#define COL_NULLABLE_Type VT_BOOL
#define COL_PRIMARYKEY_Type VT_BOOL
#define COL_UNIQUE_Type VT_BOOL
#define COLUMNDEFINITION_Type VT_I4
#define COLUMNRESTRICT_Type VT_BOOL
#define COMMANDTIMEOUT_Type VT_I4
#define COMMITPRESERVE_Type VT_BOOL
#define CONCATNULLBEHAVIOR_Type VT_I4
#define CURRENTCATALOG_Type VT_BSTR
#define DATASOURCENAME_Type VT_BSTR
#define DATASOURCEREADONLY_Type VT_BOOL
#define DBMSNAME_Type VT_BSTR
#define DBMSVER_Type VT_BSTR
#define DEFERRED_Type VT_BOOL
#define DELAYSTORAGEOBJECTS_Type VT_BOOL
#define DSOTHREADMODEL_Type VT_I4
#define GROUPBY_Type VT_I4
#define HETEROGENEOUSTABLES_Type VT_I4
#define IAccessor_Type VT_BOOL
#define IColumnsInfo_Type VT_BOOL
#define IColumnsRowset_Type VT_BOOL
#define IConnectionPointContainer_Type VT_BOOL
#define IConvertType_Type VT_BOOL
#define IRowset_Type VT_BOOL
#define IRowsetChange_Type VT_BOOL
#define IRowsetIdentity_Type VT_BOOL
#define IRowsetIndex_Type VT_BOOL
#define IRowsetInfo_Type VT_BOOL
#define IRowsetLocate_Type VT_BOOL
#define IRowsetResynch_Type VT_BOOL
#define IRowsetScroll_Type VT_BOOL
#define IRowsetUpdate_Type VT_BOOL
#define ISupportErrorInfo_Type VT_BOOL
#define ILockBytes_Type VT_BOOL
#define ISequentialStream_Type VT_BOOL
#define IStorage_Type VT_BOOL
#define IStream_Type VT_BOOL
#define IDENTIFIERCASE_Type VT_I4
#define IMMOBILEROWS_Type VT_BOOL
#define INDEX_AUTOUPDATE_Type VT_BOOL
#define INDEX_CLUSTERED_Type VT_BOOL
#define INDEX_FILLFACTOR_Type VT_I4
#define INDEX_INITIALSIZE_Type VT_I4
#define INDEX_NULLCOLLATION_Type VT_I4
#define INDEX_NULLS_Type VT_I4
#define INDEX_PRIMARYKEY_Type VT_BOOL
#define INDEX_SORTBOOKMARKS_Type VT_BOOL
#define INDEX_TEMPINDEX_Type VT_BOOL
#define INDEX_TYPE_Type VT_I4
#define INDEX_UNIQUE_Type VT_BOOL
#define INIT_DATASOURCE_Type VT_BSTR
#ifdef _WIN64
#define INIT_HWND_Type VT_I8
#else
#define INIT_HWND_Type VT_I4
#endif
#define INIT_IMPERSONATION_LEVEL_Type VT_I4
#define INIT_LCID_Type VT_I4
#define INIT_LOCATION_Type VT_BSTR
#define INIT_MODE_Type VT_I4
#define INIT_PROMPT_Type VT_I2
#define INIT_PROTECTION_LEVEL_Type VT_I4
#define INIT_PROVIDERSTRING_Type VT_BSTR
#define INIT_TIMEOUT_Type VT_I4
#define LITERALBOOKMARKS_Type VT_BOOL
#define LITERALIDENTITY_Type VT_BOOL
#define MAXINDEXSIZE_Type VT_I4
#define MAXOPENROWS_Type VT_I4
#define MAXPENDINGROWS_Type VT_I4
#define MAXROWS_Type VT_I4
#define MAXROWSIZE_Type VT_I4
#define MAXROWSIZEINCLUDESBLOB_Type VT_BOOL
#define MAXTABLESINSELECT_Type VT_I4
#define MAYWRITECOLUMN_Type VT_BOOL
#define MEMORYUSAGE_Type VT_I4
#define MULTIPLEPARAMSETS_Type VT_BOOL
#define MULTIPLERESULTS_Type VT_I4
#define MULTIPLESTORAGEOBJECTS_Type VT_BOOL
#define MULTITABLEUPDATE_Type VT_BOOL
#define NOTIFICATIONGRANULARITY_Type VT_I4
#define NOTIFICATIONPHASES_Type VT_I4
#define NOTIFYCOLUMNSET_Type VT_I4
#define NOTIFYROWDELETE_Type VT_I4
#define NOTIFYROWFIRSTCHANGE_Type VT_I4
#define NOTIFYROWINSERT_Type VT_I4
#define NOTIFYROWRESYNCH_Type VT_I4
#define NOTIFYROWSETRELEASE_Type VT_I4
#define NOTIFYROWSETFETCHPOSITIONCHANGE_Type VT_I4
#define NOTIFYROWUNDOCHANGE_Type VT_I4
#define NOTIFYROWUNDODELETE_Type VT_I4
#define NOTIFYROWUNDOINSERT_Type VT_I4
#define NOTIFYROWUPDATE_Type VT_I4
#define NULLCOLLATION_Type VT_I4
#define OLEOBJECTS_Type VT_I4
#define ORDERBYCOLUMNSINSELECT_Type VT_BOOL
#define ORDEREDBOOKMARKS_Type VT_BOOL
#define OTHERINSERT_Type VT_BOOL
#define OTHERUPDATEDELETE_Type VT_BOOL
#define OUTPUTPARAMETERAVAILABILITY_Type VT_I4
#define OWNINSERT_Type VT_BOOL
#define OWNUPDATEDELETE_Type VT_BOOL
#define PERSISTENTIDTYPE_Type VT_I4
#define PREPAREABORTBEHAVIOR_Type VT_I4
#define PREPARECOMMITBEHAVIOR_Type VT_I4
#define PROCEDURETERM_Type VT_BSTR
#define PROVIDERNAME_Type VT_BSTR
#define PROVIDEROLEDBVER_Type VT_BSTR
#define PROVIDERVER_Type VT_BSTR
#define QUICKRESTART_Type VT_BOOL
#define QUOTEDIDENTIFIERCASE_Type VT_I4
#define REENTRANTEVENTS_Type VT_BOOL
#define REMOVEDELETED_Type VT_BOOL
#define REPORTMULTIPLECHANGES_Type VT_BOOL
#define RETURNPENDINGINSERTS_Type VT_BOOL
#define ROWRESTRICT_Type VT_BOOL
#define ROWSETCONVERSIONSONCOMMAND_Type VT_BOOL
#define ROWTHREADMODEL_Type VT_I4
#define SCHEMATERM_Type VT_BSTR
#define SCHEMAUSAGE_Type VT_I4
#define SERVERCURSOR_Type VT_BOOL
#define SESS_AUTOCOMMITISOLEVELS_Type VT_I4
#define SQLSUPPORT_Type VT_I4
#define STRONGIDENTITY_Type VT_BOOL
#define STRUCTUREDSTORAGE_Type VT_I4
#define SUBQUERIES_Type VT_I4
#define SUPPORTEDTXNDDL_Type VT_I4
#define SUPPORTEDTXNISOLEVELS_Type VT_I4
#define SUPPORTEDTXNISORETAIN_Type VT_I4
#define TABLETERM_Type VT_BSTR
#define TBL_TEMPTABLE_Type VT_BOOL
#define TRANSACTEDOBJECT_Type VT_BOOL
#define UPDATABILITY_Type VT_I4
#define USERNAME_Type VT_BSTR
// 1.5
#define FILTERCOMPAREOPS_Type VT_I4
#define FINDCOMPAREOPS_Type VT_I4
#define IChapteredRowset_Type VT_BOOL
#define IDBAsynchStatus_Type VT_BOOL
#define IRowsetFind_Type VT_BOOL
#define IRowsetView_Type VT_BOOL
#define IViewChapter_Type VT_BOOL
#define IViewFilter_Type VT_BOOL
#define IViewRowset_Type VT_BOOL
#define IViewSort_Type VT_BOOL
#define INIT_ASYNCH_Type VT_I4
#define MAXOPENCHAPTERS_Type VT_I4
#define MAXORSINFILTER_Type VT_I4
#define MAXSORTCOLUMNS_Type VT_I4
#define ROWSET_ASYNCH_Type VT_I4
#define SORTONINDEX_Type VT_BOOL
// 2.0
#define IMultipleResults_Flags_Type VT_BOOL
#define DATASOURCE_TYPE_Flags_Type VT_I4
//MDPROP
#define AXES_Type VT_I4
#define FLATTENING_SUPPORT_Type VT_I4
#define MDX_JOINCUBES_Type VT_I4
#define NAMED_LEVELS_Type VT_I4
//#define RANGEROWSET_Type
#define MDX_SLICER_Type VT_I4
//#define MDX_CUBEQUALIFICATION_Type
#define MDX_OUTERREFERENCE_Type VT_I4
#define MDX_QUERYBYPROPERTY_Type VT_BOOL
#define MDX_CASESUPPORT_Type VT_I4
#define MDX_STRING_COMPOP_Type VT_I4
#define MDX_DESCFLAGS_Type VT_I4
#define MDX_SET_FUNCTIONS_Type VT_I4
#define MDX_MEMBER_FUNCTIONS_Type VT_I4
#define MDX_NUMERIC_FUNCTIONS_Type VT_I4
#define MDX_FORMULAS_Type VT_I4
#define AGGREGATECELL_UPDATE_Type VT_I4
//#define MDX_AGGREGATECELL_UPDATE_Type
#define MDX_OBJQUALIFICATION_Type VT_I4
#define MDX_NONMEASURE_EXPRESSONS_Type VT_I4
// DBPROP
#define ACCESSORDER_Type VT_I4
#define BOOKMARKINFO_Type VT_I4
#define INIT_CATALOG_Type VT_BSTR
#define ROW_BULKOPS_Type VT_I4
#define PROVIDERFRIENDLYNAME_Type VT_BSTR
#define LOCKMODE_Type VT_I4
#define MULTIPLECONNECTIONS_Type VT_BOOL
#define UNIQUEROWS_Type VT_BOOL
#define SERVERDATAONINSERT_Type VT_BOOL
//#define STORAGEFLAGS_Type
#define CONNECTIONSTATUS_Type VT_I4
#define ALTERCOLUMN_Type VT_I4
#define COLUMNLCID_Type VT_I4
#define RESETDATASOURCE_Type VT_I4
#define INIT_OLEDBSERVICES_Type VT_I4
#define IRowsetRefresh_Type VT_BOOL
#define SERVERNAME_Type VT_BSTR
#define IParentRowset_Type VT_BOOL
#define HIDDENCOLUMNS_Type VT_I4
#define PROVIDERMEMORY_Type VT_BOOL
#define CLIENTCURSOR_Type VT_BOOL
// 2.1
#define TRUSTEE_USERNAME_Type VT_BSTR
#define TRUSTEE_AUTHENTICATION_Type VT_BSTR
#define TRUSTEE_NEWAUTHENTICATION_Type VT_BSTR
#define IRow_Type VT_BOOL
#define IRowChange_Type VT_BOOL
#define IRowSchemaChange_Type VT_BOOL
#define IGetRow_Type VT_BOOL
#define IScopedOperations_Type VT_BOOL
#define IBindResource_Type VT_BOOL
#define ICreateRow_Type VT_BOOL
#define INIT_BINDFLAGS_Type VT_I4
#define INIT_LOCKOWNER_Type VT_BSTR
#define GENERATEURL_Type VT_I4
//#define IDBBinderProperties_Type
#define IColumnsInfo2_Type VT_BOOL
//#define IRegisterProvider_Type
#define IGetSession_Type VT_BOOL
#define IGetSourceRow_Type VT_BOOL
#define IRowsetCurrentIndex_Type VT_BOOL
#define OPENROWSETSUPPORT_Type VT_I4
#define COL_ISLONG_Type VT_BOOL
// 2.5
//#define COL_SEED_Type VT_VARIANT //!!!
//#define COL_INCREMENT_Type VT_VARIANT //!!!
#define INIT_GENERALTIMEOUT_Type VT_I4
#define COMSERVICES_Type VT_I4
// 2.6
#define OUTPUTSTREAM_Type VT_UNKNOWN
#define OUTPUTENCODING_Type VT_BSTR
#define TABLESTATISTICS_Type VT_I4
#define SKIPROWCOUNTRESULTS_Type VT_BOOL
#define IRowsetBookmark_Type VT_BOOL
#define VISUALMODE_Type VT_I4



#define ABORTPRESERVE_Value ATL_VARIANT_FALSE
#define ACTIVESESSIONS_Value 0
#define APPENDONLY_Value ATL_VARIANT_FALSE
#define ASYNCTXNABORT_Value ATL_VARIANT_FALSE
#define ASYNCTXNCOMMIT_Value ATL_VARIANT_FALSE
#define AUTH_CACHE_AUTHINFO_Value ATL_VARIANT_FALSE
#define AUTH_ENCRYPT_PASSWORD_Value ATL_VARIANT_FALSE
#define AUTH_INTEGRATED_Value OLESTR("")
#define AUTH_MASK_PASSWORD_Value ATL_VARIANT_FALSE
#define AUTH_PASSWORD_Value OLESTR("")
#define AUTH_PERSIST_ENCRYPTED_Value ATL_VARIANT_FALSE
#define AUTH_PERSIST_SENSITIVE_AUTHINFO_Value ATL_VARIANT_FALSE
#define AUTH_USERID_Value OLESTR("")
#define BLOCKINGSTORAGEOBJECTS_Value ATL_VARIANT_FALSE
#define BOOKMARKS_Value ATL_VARIANT_FALSE
#define BOOKMARKSKIPPED_Value ATL_VARIANT_FALSE
#define BOOKMARKTYPE_Value 0
#define BYREFACCESSORS_Value ATL_VARIANT_FALSE
#define CACHEDEFERRED_Value ATL_VARIANT_FALSE
#define CANFETCHBACKWARDS_Value ATL_VARIANT_TRUE
#define CANHOLDROWS_Value ATL_VARIANT_TRUE
#define CANSCROLLBACKWARDS_Value ATL_VARIANT_TRUE
#define CATALOGLOCATION_Value 0
#define CATALOGTERM_Value OLESTR("")
#define CATALOGUSAGE_Value 0
#define CHANGEINSERTEDROWS_Value ATL_VARIANT_FALSE
#define COL_AUTOINCREMENT_Value ATL_VARIANT_FALSE
#define COL_DEFAULT_Value OLESTR("")
#define COL_DESCRIPTION_Value OLESTR("")
#define COL_FIXEDLENGTH_Value ATL_VARIANT_FALSE
#define COL_NULLABLE_Value ATL_VARIANT_FALSE
#define COL_PRIMARYKEY_Value ATL_VARIANT_FALSE
#define COL_UNIQUE_Value ATL_VARIANT_FALSE
#define COLUMNDEFINITION_Value 0
#define COLUMNRESTRICT_Value ATL_VARIANT_FALSE
#define COMMANDTIMEOUT_Value 0
#define COMMITPRESERVE_Value ATL_VARIANT_FALSE
#define CONCATNULLBEHAVIOR_Value 0
#define CURRENTCATALOG_Value OLESTR("")
#define DATASOURCENAME_Value OLESTR("")
#define DATASOURCEREADONLY_Value ATL_VARIANT_TRUE
#define DBMSNAME_Value OLESTR("")
#define DBMSVER_Value OLESTR("")
#define DEFERRED_Value ATL_VARIANT_FALSE
#define DELAYSTORAGEOBJECTS_Value ATL_VARIANT_FALSE
#define DSOTHREADMODEL_Value DBPROPVAL_RT_APTMTTHREAD
#define GROUPBY_Value 0
#define HETEROGENEOUSTABLES_Value 0
#define IAccessor_Value ATL_VARIANT_TRUE
#define IColumnsInfo_Value ATL_VARIANT_TRUE
#define IColumnsRowset_Value ATL_VARIANT_FALSE
#define IConnectionPointContainer_Value ATL_VARIANT_FALSE
#define IConvertType_Value ATL_VARIANT_TRUE
#define IRowset_Value ATL_VARIANT_TRUE
#define IRowsetChange_Value ATL_VARIANT_FALSE
#define IRowsetIdentity_Value ATL_VARIANT_TRUE
#define IRowsetIndex_Value ATL_VARIANT_FALSE
#define IRowsetInfo_Value ATL_VARIANT_TRUE
#define IRowsetLocate_Value ATL_VARIANT_FALSE
#define IRowsetResynch_Value ATL_VARIANT_FALSE
#define IRowsetScroll_Value ATL_VARIANT_FALSE
#define IRowsetUpdate_Value ATL_VARIANT_FALSE
#define ISupportErrorInfo_Value ATL_VARIANT_FALSE
#define ILockBytes_Value ATL_VARIANT_FALSE
#define ISequentialStream_Value ATL_VARIANT_FALSE
#define IStorage_Value ATL_VARIANT_FALSE
#define IStream_Value ATL_VARIANT_FALSE
#define IDENTIFIERCASE_Value 0
#define IMMOBILEROWS_Value ATL_VARIANT_FALSE
#define INDEX_AUTOUPDATE_Value ATL_VARIANT_FALSE
#define INDEX_CLUSTERED_Value ATL_VARIANT_FALSE
#define INDEX_FILLFACTOR_Value 0
#define INDEX_INITIALSIZE_Value 0
#define INDEX_NULLCOLLATION_Value 0
#define INDEX_NULLS_Value 0
#define INDEX_PRIMARYKEY_Value ATL_VARIANT_FALSE
#define INDEX_SORTBOOKMARKS_Value ATL_VARIANT_FALSE
#define INDEX_TEMPINDEX_Value ATL_VARIANT_FALSE
#define INDEX_TYPE_Value 0
#define INDEX_UNIQUE_Value ATL_VARIANT_FALSE
#define INIT_DATASOURCE_Value OLESTR("")
#define INIT_HWND_Value 0
#define INIT_IMPERSONATION_LEVEL_Value 0
#define INIT_LCID_Value 0
#define INIT_LOCATION_Value OLESTR("")
#define INIT_MODE_Value DB_MODE_READ
#define INIT_PROMPT_Value DBPROMPT_NOPROMPT
#define INIT_PROTECTION_LEVEL_Value 0
#define INIT_PROVIDERSTRING_Value OLESTR("")
#define INIT_TIMEOUT_Value 0
#define LITERALBOOKMARKS_Value ATL_VARIANT_FALSE
#define LITERALIDENTITY_Value ATL_VARIANT_FALSE
#define MAXINDEXSIZE_Value 0
#define MAXOPENROWS_Value 0
#define MAXPENDINGROWS_Value 0
#define MAXROWS_Value 0
#define MAXROWSIZE_Value 0
#define MAXROWSIZEINCLUDESBLOB_Value ATL_VARIANT_FALSE
#define MAXTABLESINSELECT_Value 0
#define MAYWRITECOLUMN_Value ATL_VARIANT_FALSE
#define MEMORYUSAGE_Value 0
#define MULTIPLEPARAMSETS_Value ATL_VARIANT_FALSE
#define MULTIPLERESULTS_Value 0
#define MULTIPLESTORAGEOBJECTS_Value ATL_VARIANT_FALSE
#define MULTITABLEUPDATE_Value ATL_VARIANT_FALSE
#define NOTIFICATIONGRANULARITY_Value DBPROPVAL_NT_SINGLEROW
#define NOTIFICATIONPHASES_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER | DBPROPVAL_NP_FAILEDTODO | DBPROPVAL_NP_DIDEVENT
#define NOTIFYCOLUMNSET_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NOTIFYROWDELETE_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NOTIFYROWFIRSTCHANGE_Value DBPROPVAL_NP_OKTODO | DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NOTIFYROWINSERT_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NOTIFYROWRESYNCH_Value 0
#define NOTIFYROWSETRELEASE_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NOTIFYROWSETFETCHPOSITIONCHANGE_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NOTIFYROWUNDOCHANGE_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NOTIFYROWUNDODELETE_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NOTIFYROWUNDOINSERT_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NOTIFYROWUPDATE_Value DBPROPVAL_NP_OKTODO |	DBPROPVAL_NP_ABOUTTODO | DBPROPVAL_NP_SYNCHAFTER
#define NULLCOLLATION_Value 0
#define OLEOBJECTS_Value 0
#define ORDERBYCOLUMNSINSELECT_Value ATL_VARIANT_FALSE
#define ORDEREDBOOKMARKS_Value ATL_VARIANT_FALSE
#define OTHERINSERT_Value ATL_VARIANT_FALSE
#define OTHERUPDATEDELETE_Value ATL_VARIANT_FALSE
#define OUTPUTPARAMETERAVAILABILITY_Value 0
#define OWNINSERT_Value ATL_VARIANT_FALSE
#define OWNUPDATEDELETE_Value ATL_VARIANT_FALSE
#define PERSISTENTIDTYPE_Value 0
#define PREPAREABORTBEHAVIOR_Value 0
#define PREPARECOMMITBEHAVIOR_Value 0
#define PROCEDURETERM_Value OLESTR("")
#define PROVIDERNAME_Value OLESTR("")
#define PROVIDEROLEDBVER_Value OLESTR("02.60")
#define PROVIDERVER_Value OLESTR("01.00")
#define QUICKRESTART_Value ATL_VARIANT_FALSE
#define QUOTEDIDENTIFIERCASE_Value 0
#define REENTRANTEVENTS_Value ATL_VARIANT_FALSE
#define REMOVEDELETED_Value ATL_VARIANT_FALSE
#define REPORTMULTIPLECHANGES_Value ATL_VARIANT_FALSE
#define RETURNPENDINGINSERTS_Value ATL_VARIANT_FALSE
#define ROWRESTRICT_Value ATL_VARIANT_FALSE
#define ROWSETCONVERSIONSONCOMMAND_Value ATL_VARIANT_TRUE
#define ROWTHREADMODEL_Value 0
#define SCHEMATERM_Value OLESTR("")
#define SCHEMAUSAGE_Value 0
#define SERVERCURSOR_Value ATL_VARIANT_FALSE
#define SESS_AUTOCOMMITISOLEVELS_Value 0
#define SQLSUPPORT_Value 0
#define STRONGIDENTITY_Value ATL_VARIANT_FALSE
#define STRUCTUREDSTORAGE_Value 0
#define SUBQUERIES_Value 0
#define SUPPORTEDTXNDDL_Value 0
#define SUPPORTEDTXNISOLEVELS_Value 0
#define SUPPORTEDTXNISORETAIN_Value 0
#define TABLETERM_Value OLESTR("")
#define TBL_TEMPTABLE_Value ATL_VARIANT_FALSE
#define TRANSACTEDOBJECT_Value ATL_VARIANT_FALSE
#define UPDATABILITY_Value 0
#define USERNAME_Value OLESTR("")
// 1.5
#define FILTERCOMPAREOPS_Value 0
#define FINDCOMPAREOPS_Value 0
#define IChapteredRowset_Value ATL_VARIANT_FALSE
#define IDBAsynchStatus_Value ATL_VARIANT_FALSE
#define IRowsetFind_Value ATL_VARIANT_FALSE
#define IRowsetView_Value ATL_VARIANT_FALSE
#define IViewChapter_Value ATL_VARIANT_FALSE
#define IViewFilter_Value ATL_VARIANT_FALSE
#define IViewRowset_Value ATL_VARIANT_FALSE
#define IViewSort_Value ATL_VARIANT_FALSE
#define INIT_ASYNCH_Value 0
#define MAXOPENCHAPTERS_Value 0
#define MAXORSINFILTER_Value 0
#define MAXSORTCOLUMNS_Value 0
#define ROWSET_ASYNCH_Value 0
#define SORTONINDEX_Value ATL_VARIANT_FALSE
// 2.0
#define IMultipleResults_Value ATL_VARIANT_FALSE
#define DATASOURCE_TYPE_Value 0
//MDPROP
#define AXES_Value 0
#define FLATTENING_SUPPORT_Value 0
#define MDX_JOINCUBES_Value 0
#define NAMED_LEVELS_Value 0
//#define RANGEROWSET_Value 0
#define MDX_SLICER_Value 0
//#define MDX_CUBEQUALIFICATION_Value
#define MDX_OUTERREFERENCE_Value 0
#define MDX_QUERYBYPROPERTY_Value ATL_VARIANT_FALSE
#define MDX_CASESUPPORT_Value 0
#define MDX_STRING_COMPOP_Value 0
#define MDX_DESCFLAGS_Value 0
#define MDX_SET_FUNCTIONS_Value 0
#define MDX_MEMBER_FUNCTIONS_Value 0
#define MDX_NUMERIC_FUNCTIONS_Value 0
#define MDX_FORMULAS_Value 0
#define AGGREGATECELL_UPDATE_Value 0
//#define MDX_AGGREGATECELL_UPDATE_Value
#define MDX_OBJQUALIFICATION_Value 0
#define MDX_NONMEASURE_EXPRESSONS_Value 0
// DBPROP
#define ACCESSORDER_Value 0
#define BOOKMARKINFO_Value 0
#define INIT_CATALOG_Value OLESTR("")
#define ROW_BULKOPS_Value 0
#define PROVIDERFRIENDLYNAME_Value OLESTR("")
#define LOCKMODE_Value 0
#define MULTIPLECONNECTIONS_Value ATL_VARIANT_FALSE
#define UNIQUEROWS_Value ATL_VARIANT_FALSE
#define SERVERDATAONINSERT_Value ATL_VARIANT_FALSE
//#define STORAGEFLAGS_Value
#define CONNECTIONSTATUS_Value 0
#define ALTERCOLUMN_Value 0
#define COLUMNLCID_Value 0
#define RESETDATASOURCE_Value 0
#define INIT_OLEDBSERVICES_Value 0
#define IRowsetRefresh_Value ATL_VARIANT_FALSE
#define SERVERNAME_Value OLESTR("")
#define IParentRowset_Value ATL_VARIANT_FALSE
#define HIDDENCOLUMNS_Value 0
#define PROVIDERMEMORY_Value ATL_VARIANT_FALSE
#define CLIENTCURSOR_Value ATL_VARIANT_FALSE
// 2.1
#define TRUSTEE_USERNAME_Value OLESTR("")
#define TRUSTEE_AUTHENTICATION_Value OLESTR("")
#define TRUSTEE_NEWAUTHENTICATION_Value OLESTR("")
#define IRow_Value ATL_VARIANT_FALSE
#define IRowChange_Value ATL_VARIANT_FALSE
#define IRowSchemaChange_Value ATL_VARIANT_FALSE
#define IGetRow_Value ATL_VARIANT_FALSE
#define IScopedOperations_Value ATL_VARIANT_FALSE
#define IBindResource_Value ATL_VARIANT_FALSE
#define ICreateRow_Value ATL_VARIANT_FALSE
#define INIT_BINDFLAGS_Value 0
#define INIT_LOCKOWNER_Value OLESTR("")
#define GENERATEURL_Value 0
//#define IDBBinderProperties_Value
#define IColumnsInfo2_Value ATL_VARIANT_FALSE
//#define IRegisterProvider_Value
#define IGetSession_Value ATL_VARIANT_FALSE
#define IGetSourceRow_Value ATL_VARIANT_FALSE
#define IRowsetCurrentIndex_Value ATL_VARIANT_FALSE
#define OPENROWSETSUPPORT_Value 0
#define COL_ISLONG_Value ATL_VARIANT_FALSE
// 2.5
//#define COL_SEED_Value
//#define COL_INCREMENT_Value
#define INIT_GENERALTIMEOUT_Value 0
#define COMSERVICES_Value 0
// 2.6
#define OUTPUTSTREAM_Value NULL
#define OUTPUTENCODING_Value NULL
#define TABLESTATISTICS_Value 0
#define SKIPROWCOUNTRESULTS_Value ATL_VARIANT_FALSE
#define IRowsetBookmark_Value ATL_VARIANT_FALSE
#define VISUALMODE_Value MDPROPVAL_VISUAL_MODE_DEFAULT


#define OUT_OF_LINE virtual

#define BEGIN_PROPSET_MAP(Class) \
static ATL::UPROPSET* _GetPropSet( \
	_Out_opt_ ULONG* pNumPropSets,\
	_Out_ ULONG* pcElemPerSupported, \
	_Inout_opt_ ATL::UPROPSET* pSet = NULL, \
	_Inout_opt_ GUID* pguidSet = NULL) \
{ \
	typedef Class _PropSetClass; \
	USES_ATL_SAFE_ALLOCA;\
	ULONG& cElemsMax = *pcElemPerSupported; \
	cElemsMax = 0; \
	ULONG nCurProp = 0; \
	int cRemainder = 0; \
	if (pguidSet == NULL) \
		pguidSet = (GUID*)&GUID_NULL;

#define BEGIN_PROPERTY_SET_EX(guid, flags) \
if (pNumPropSets != NULL) \
{ \
	ATLASSUME(pSet != NULL); \
	pSet[nCurProp].pPropSet = &guid; \
	pSet[nCurProp].dwFlags = flags; \
	pSet[nCurProp].bIsChained = false; \
} \
static const ATL::UPROPINFO aProperty##guid[] = \
{

#define BEGIN_PROPERTY_SET(guid) BEGIN_PROPERTY_SET_EX(guid, 0)

//DBPROP_ macros

#define PROPERTY_INFO_ENTRY_EX(dwPropID, vt, dwFlags, value, options) DBPROP_##dwPropID, IDS_DBPROP_##dwPropID, vt, dwFlags, (DWORD_PTR)value, (DBPROPOPTIONS)options,

#define PROPERTY_INFO_ENTRY_VALUE(dwPropID, value) PROPERTY_INFO_ENTRY_EX(dwPropID, dwPropID##_Type, dwPropID##_Flags, value, 0)

#define PROPERTY_INFO_ENTRY_VALUE_FLAGS(dwPropID, value, dwFlags) PROPERTY_INFO_ENTRY_EX(dwPropID, dwPropID##_Type, dwFlags, value, 0)

#define PROPERTY_INFO_ENTRY(dwPropID) PROPERTY_INFO_ENTRY_VALUE(dwPropID, dwPropID##_Value)


//MDPROP_ macros

#define MDPROPERTY_INFO_ENTRY_EX(dwPropID, vt, dwFlags, value, options) MDPROP_##dwPropID, IDS_MDPROP_##dwPropID, vt, dwFlags, (DWORD_PTR)value, (DBPROPOPTIONS)options,

#define MDPROPERTY_INFO_ENTRY_VALUE(dwPropID, value) MDPROPERTY_INFO_ENTRY_EX(dwPropID, dwPropID##_Type, dwPropID##_Flags, value, 0)

#define MDPROPERTY_INFO_ENTRY_VALUE_FLAGS(dwPropID, value, dwFlags) MDPROPERTY_INFO_ENTRY_EX(dwPropID, dwPropID##_Type, dwFlags, value, 0)

#define MDPROPERTY_INFO_ENTRY(dwPropID) MDPROPERTY_INFO_ENTRY_VALUE(dwPropID, dwPropID##_Value)


#define END_PROPERTY_SET(guid) \
		}; \
		if (pNumPropSets != NULL) \
		{ \
			pSet[nCurProp].pUPropInfo = (ATL::UPROPINFO*)aProperty##guid; \
			pSet[nCurProp].cUPropInfo = sizeof(aProperty##guid) / sizeof(ATL::UPROPINFO); \
			cRemainder = (pSet[nCurProp].cUPropInfo % 32) ? 1 : 0; \
			if (cElemsMax < (pSet[nCurProp].cUPropInfo / 32 + cRemainder)) \
			{ \
				cElemsMax = (pSet[nCurProp].cUPropInfo / 32 + cRemainder); \
			} \
		} \
		nCurProp++;

#define CHAIN_PROPERTY_SET(ChainClass) \
		ULONG cPropSets##ChainClass, cElsSupported##ChainClass; \
		int cSets##ChainClass = (int)(DWORD_PTR)ChainClass::_GetPropSet(NULL, &cElsSupported##ChainClass); \
		if (pNumPropSets != NULL) \
		{ \
			ATL::UPROPSET* pSetA=(ATL::UPROPSET*)_ATL_SAFE_ALLOCA(::ATL::AtlMultiplyThrow(static_cast<int>(sizeof(ATL::UPROPSET)),cSets##ChainClass),_ATL_SAFE_ALLOCA_DEF_THRESHOLD);\
			ATL::UPROPSET* pSetTemp = ChainClass::_GetPropSet(&cPropSets##ChainClass, &cElsSupported##ChainClass, pSetA); \
			cElemsMax = (cElemsMax < cElsSupported##ChainClass) ? cElsSupported##ChainClass : cElemsMax; \
			ATLENSURE(pSetTemp); \
			for (ULONG iSet = nCurProp; iSet < nCurProp+cPropSets##ChainClass; iSet++) \
			{ \
				pSet[iSet].pPropSet = pSetTemp[iSet-nCurProp].pPropSet; \
				pSet[iSet].dwFlags = pSetTemp[iSet-nCurProp].dwFlags; \
				pSet[iSet].pUPropInfo = pSetTemp[iSet-nCurProp].pUPropInfo; \
				pSet[iSet].cUPropInfo = pSetTemp[iSet-nCurProp].cUPropInfo; \
				pSet[iSet].bIsChained = true; \
			} \
		} \
		nCurProp += cSets##ChainClass;

#define END_PROPSET_MAP() \
	if (pNumPropSets != NULL) \
	{ \
		if (IsEqualGUID(*pguidSet, GUID_NULL)) \
		{ \
			*pNumPropSets = nCurProp; \
			return pSet; \
		} \
		else \
		{ \
			*pNumPropSets = 1; \
			UINT i = 0; \
			for (; i < nCurProp && IsEqualGUID(*(pSet[i].pPropSet), *pguidSet); i++); \
			return (i == nCurProp ) ? &pSet[0] : &pSet[i]; \
		} \
	} \
	return (ATL::UPROPSET*)(DWORD_PTR)nCurProp; \
	}


// For DataSource flags IDBInitialize::m_dwStatus
enum DATASOURCE_FLAGS {
	DSF_MASK_INIT			= 0xFFFFF00F,	// Mask for stuff lasting over init/uninit.
	DSF_PERSIST_DIRTY		= 0x00000001,	// Set if init string changes.
	DSF_INITIALIZED			= 0x00000010,	// Have we been initialized.
};


#define DBID_USE_GUID_OR_PGUID(e) \
	((1<<(e)) & \
	( 1<<DBKIND_GUID \
	| 1<<DBKIND_GUID_NAME \
	| 1<<DBKIND_GUID_PROPID \
	| 1<<DBKIND_PGUID_NAME \
	| 1<<DBKIND_PGUID_PROPID ))

#define DBID_USE_GUID(e) \
	((1<<(e)) & \
	( 1<<DBKIND_GUID \
	| 1<<DBKIND_GUID_NAME \
	| 1<<DBKIND_GUID_PROPID ))

#define DBID_USE_PGUID(e) \
	((1<<(e)) & \
	( 1<<DBKIND_PGUID_NAME \
	| 1<<DBKIND_PGUID_PROPID ))

#define DBID_USE_NAME(e) \
	((1<<(e)) & \
	( 1<<DBKIND_NAME \
	| 1<<DBKIND_GUID_NAME \
	| 1<<DBKIND_PGUID_NAME ))

#define DBID_USE_PROPID(e) \
	((1<<(e)) & \
	( 1<<DBKIND_PROPID \
	| 1<<DBKIND_GUID_PROPID \
	| 1<<DBKIND_PGUID_PROPID ))

// Bookmark can be either guid or pguid.
#define DBID_IS_BOOKMARK(dbid) \
	(  DBID_USE_GUID(dbid.eKind)  &&  dbid.uGuid.guid  == DBCOL_SPECIALCOL \
	|| DBID_USE_PGUID(dbid.eKind) && *dbid.uGuid.pguid == DBCOL_SPECIALCOL )

#define DivDword(dw) (dw >> 5)		// dw / 32 = dw / (sizeof(DWORD)*8)
#define ModDword(dw) (dw & (32-1))	// dw % 32
#define DwordSizeofBits(nBits) (nBits/32+1)	// Use in array declarations
#define CLEARBITARRAY( rgdwFlags ) memset( rgdwFlags, 0, sizeof(rgdwFlags) )

template <class T>
BOOL InRange(
	_In_ const T& val,
	_In_ const T& valMin,
	_In_ const T& valMax)
{
	return ( valMin <= val && val <= valMax );
}
// Implementation Class
class CBitFieldOps
{
public:
	void SETBIT(
		_In_ DWORD rgdwFlags[],
		_In_ const DWORD dwBit)
	{
		rgdwFlags[DivDword(dwBit)] |= 1 << ModDword(dwBit);
	}

	void CLEARBIT(
		_In_ DWORD rgdwFlags[],
		_In_ const DWORD dwBit)
	{
		rgdwFlags[DivDword(dwBit)] &= ~( 1 << ModDword(dwBit) );
	}

	DWORD TESTBIT(
		_In_ const DWORD rgdwFlags[],
		_In_ const DWORD dwBit)
	{
		//old//Note: Not {0,1}, but from {0...2^32-1}.
		// Note: Now returns {0,1}.
		return ( rgdwFlags[DivDword(dwBit)] & ( 1 << ModDword(dwBit) ) ) != 0;
	}
};

// Implementation Class
class CDBIDOps
{
public:
	HRESULT CompareDBIDs(
		_In_opt_ const DBID* pdbid1,
		_In_opt_ const DBID* pdbid2)
	{
		// Array of valid eKind matches, in addition to matching exactly.
		static const BYTE s_rgbKind[] =
		{
			DBKIND_PGUID_NAME,		// DBKIND_GUID_NAME
			DBKIND_PGUID_PROPID,	// DBKIND_GUID_PROPID
			DBKIND_NAME,			// DBKIND_NAME
			DBKIND_GUID_NAME,		// DBKIND_PGUID_NAME
			DBKIND_GUID_PROPID,		// DBKIND_PGUID_PROPID
			DBKIND_PROPID,			// DBKIND_PROPID
			DBKIND_GUID				// DBKIND_GUID
		};

		if( !pdbid1 || !pdbid2 )
			return S_FALSE;

		// Assume a match, and discard early if we can.
		DBKIND tmp1 = 0;
		DBKIND tmp2 = (DBKIND)(_countof(s_rgbKind) - 1);
		if (!InRange(pdbid2->eKind, tmp1, tmp2))
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("Column ID out of Range\n"));
			return E_FAIL;
		}

		ATLASSUME(pdbid2->eKind < _countof(s_rgbKind));
		if (pdbid1->eKind != pdbid2->eKind &&
			pdbid1->eKind != s_rgbKind[pdbid2->eKind])
		{
			return S_FALSE;
		}

		if (DBID_USE_GUID_OR_PGUID(pdbid1->eKind))
		{
			if (!DBID_USE_GUID_OR_PGUID(pdbid2->eKind))
				return S_FALSE;
			// Compare GUIDs.
			// Note that _GUID_ is equivalent to _PGUID_.
			if (!InlineIsEqualGUID(
					DBID_USE_PGUID(pdbid1->eKind) ? *(pdbid1->uGuid.pguid) : pdbid1->uGuid.guid,
					DBID_USE_PGUID(pdbid2->eKind) ? *(pdbid2->uGuid.pguid) : pdbid2->uGuid.guid ))
				return S_FALSE;
		}
		if (DBID_USE_NAME(pdbid1->eKind))
		{
			if (!DBID_USE_NAME(pdbid2->eKind))
				return S_FALSE;
			// Compare names.
			// Need to check if 1 is null and the other is not.
			if ((pdbid1->uName.pwszName == NULL) != (pdbid2->uName.pwszName == NULL))
				 return S_FALSE;
			// Since the above check does not rule out both being null, which is
			// a valid comparison, and wcscmp will GPF if they were, we need
			// to check for valid pointers
			if( pdbid1->uName.pwszName && pdbid2->uName.pwszName )
			{
				// Assume null-terminated.
				// Assume LCID match is OK (note diff with lstrcmp(), CompareString().)
				if (wcscmp(pdbid1->uName.pwszName, pdbid2->uName.pwszName) != 0)
					return S_FALSE;
			}
		}
		if (DBID_USE_PROPID(pdbid1->eKind))
		{
			if (!DBID_USE_PROPID(pdbid2->eKind))
				return S_FALSE;
			// Compare PROPID.
			if (pdbid1->uName.ulPropid != pdbid2->uName.ulPropid)
				return S_FALSE;
		}

		// No reason to discard, so must have matched each field successfully.
		return S_OK;
	}

	static HRESULT IsValidDBID(_In_ const DBID* pdbid1)
	{
		ATLASSERT( pdbid1 );

		if( pdbid1 &&
			((pdbid1->eKind == DBKIND_GUID_NAME) ||
			(pdbid1->eKind == DBKIND_GUID_PROPID) ||
			(pdbid1->eKind == DBKIND_NAME) ||
			(pdbid1->eKind == DBKIND_PGUID_NAME) ||
			(pdbid1->eKind == DBKIND_PGUID_PROPID) ||
			(pdbid1->eKind == DBKIND_PROPID) ||
			(pdbid1->eKind == DBKIND_GUID)) )
			return S_OK;
		else
			return S_FALSE;
	}
	HRESULT CopyDBIDs(
		_Out_ DBID* pdbidDest,
		_In_ const DBID* pdbidSrc)
	{
		ULONG cwchBuffer;

		ATLASSERT( pdbidDest && pdbidSrc );

		if( !pdbidDest || !pdbidSrc )
			return S_FALSE;

		// Save eKind
		pdbidDest->eKind = pdbidSrc->eKind;

		switch( pdbidSrc->eKind )
		{

			case DBKIND_GUID_NAME:
				pdbidDest->uGuid.guid = pdbidSrc->uGuid.guid;
				cwchBuffer = ocslen(pdbidSrc->uName.pwszName);
				cwchBuffer++;
				pdbidDest->uName.pwszName = (PWSTR)::ATL::AtlCoTaskMemCAlloc(cwchBuffer, static_cast<ULONG>(sizeof(WCHAR)));
				if( pdbidDest->uName.pwszName )
				{
					Checked::memcpy_s(pdbidDest->uName.pwszName, cwchBuffer * sizeof(WCHAR), pdbidSrc->uName.pwszName, cwchBuffer*sizeof(WCHAR));
				}
				else
				{
					return E_OUTOFMEMORY;
				}
				break;

			case DBKIND_GUID_PROPID:
				pdbidDest->uGuid.guid = pdbidSrc->uGuid.guid;
				pdbidDest->uName.ulPropid = pdbidSrc->uName.ulPropid;
				break;
			case DBKIND_NAME:
				cwchBuffer = ocslen(pdbidSrc->uName.pwszName);
				cwchBuffer++;
				pdbidDest->uName.pwszName = (PWSTR)::ATL::AtlCoTaskMemCAlloc(cwchBuffer, static_cast<ULONG>(sizeof(WCHAR)));
				if( pdbidDest->uName.pwszName )
				{
					Checked::memcpy_s(pdbidDest->uName.pwszName, cwchBuffer*sizeof(WCHAR), pdbidSrc->uName.pwszName, cwchBuffer*sizeof(WCHAR));
				}
				else
				{
					return E_OUTOFMEMORY;
				}
				break;
			case DBKIND_PGUID_NAME:
				pdbidDest->uGuid.pguid = (GUID*)CoTaskMemAlloc(sizeof(GUID));
				if( pdbidDest->uGuid.pguid )
				{
					*(pdbidDest->uGuid.pguid) = *(pdbidSrc->uGuid.pguid);
					cwchBuffer = ocslen(pdbidSrc->uName.pwszName);
					cwchBuffer++;
					pdbidDest->uName.pwszName = (PWSTR)::ATL::AtlCoTaskMemCAlloc(cwchBuffer, static_cast<ULONG>(sizeof(WCHAR)));
					if( pdbidDest->uName.pwszName )
					{
						Checked::memcpy_s(pdbidDest->uName.pwszName, cwchBuffer*sizeof(WCHAR), pdbidSrc->uName.pwszName, cwchBuffer*sizeof(WCHAR));
						break;
					}
					else
					{
						CoTaskMemFree(pdbidDest->uGuid.pguid);
						pdbidDest->uGuid.pguid = NULL;
					}
				}
				return E_OUTOFMEMORY;
			case DBKIND_PGUID_PROPID:
				pdbidDest->uGuid.pguid = (GUID*)CoTaskMemAlloc(sizeof(GUID));
				if( pdbidDest->uGuid.pguid )
					*(pdbidDest->uGuid.pguid) = *(pdbidSrc->uGuid.pguid);
				else
					return E_OUTOFMEMORY;
				pdbidDest->uName.ulPropid = pdbidSrc->uName.ulPropid;
				break;
			case DBKIND_PROPID:
				pdbidDest->uName.ulPropid = pdbidSrc->uName.ulPropid;
				break;
			case DBKIND_GUID:
				pdbidDest->uGuid.guid = pdbidSrc->uGuid.guid;
				break;
			default:
				ATLASSERT(L"Unhandled dbid1.ekind");
				return S_FALSE;
		}

		return S_OK;
	}
	static GUID* GetDBIDpGuid(_In_ DBID& dbid)
	{
		GUID* pGuid;
		switch (dbid.eKind)
		{
		case DBKIND_PGUID_NAME:
		case DBKIND_PGUID_PROPID:
			pGuid = dbid.uGuid.pguid;
			break;
		case DBKIND_GUID_NAME:
		case DBKIND_GUID_PROPID:
		case DBKIND_GUID:
			pGuid = &(dbid.uGuid.guid);
			break;
		default:
			pGuid = NULL;
		}

		return pGuid;
	}
	static ULONG GetPropIDFromDBID(_In_ DBID& dbid)
	{
		switch (dbid.eKind)
		{
		case DBKIND_GUID_PROPID:
		case DBKIND_PGUID_PROPID:
		case DBKIND_PROPID:
			return dbid.uName.ulPropid;
		default:
			return 0;
		}
	}
	void FreeDBIDs(_In_ DBID* pdbidSrc)
	{
		switch( pdbidSrc->eKind )
		{

			case DBKIND_GUID_NAME:
				CoTaskMemFree(pdbidSrc->uName.pwszName);
				break;
			case DBKIND_NAME:
				CoTaskMemFree(pdbidSrc->uName.pwszName);
				break;
			case DBKIND_PGUID_NAME:
				CoTaskMemFree(pdbidSrc->uGuid.pguid);
				CoTaskMemFree(pdbidSrc->uName.pwszName);
				break;
			case DBKIND_PGUID_PROPID:
				CoTaskMemFree(pdbidSrc->uGuid.pguid);
				break;
			case DBKIND_GUID_PROPID:
			case DBKIND_PROPID:
			case DBKIND_GUID:
				break;
			default:
				ATLASSERT(L"Unhandled dbid1.ekind");
				break;
		}
	}
};

class CConvertHelper
{
public:
	CConvertHelper() {}
	HRESULT FinalConstruct()
	{
		HRESULT hr = ::CoCreateInstance(CLSID_OLEDB_CONVERSIONLIBRARY, NULL,
			CLSCTX_INPROC_SERVER, __uuidof(IDataConvert), (void**)&m_spConvert);

		if (FAILED(hr))
			return hr;

		// Check to see if the data conversion routine is 2.0 capable, if so.  Initialize
		// the conversion routine to be 2.0.
		DCINFO rgInfo[] = {{DCINFOTYPE_VERSION, {VT_UI4, 0, 0, 0, 0x0}}};
		CComPtr<IDCInfo> spIDCInfo;

		hr = m_spConvert->QueryInterface(&spIDCInfo);
		if (hr == S_OK)
		{
			V_UI4(&rgInfo->vData) = 0x200;  // OLEDB Version 02.00
			spIDCInfo->SetInfo(1, rgInfo);
		}

		return hr;
	}
	CComPtr<IDataConvert> m_spConvert;
};

// IDBCreateSessionImpl
template <class T, class SessionClass>
class ATL_NO_VTABLE IDBCreateSessionImpl :
	public IDBCreateSession
{
public:
ATLPREFAST_SUPPRESS(6387)
	STDMETHOD(CreateSession)(
		_In_opt_ IUnknown *pUnkOuter,
		_In_ REFIID riid,
		_Outptr_ IUnknown **ppDBSession)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBCreateSessionImpl::CreateSession\n"));
		if (ppDBSession == NULL)
			return E_INVALIDARG;
		*ppDBSession = NULL;
		T* pT = (T*)this;
		if (!(pT->m_dwStatus & DSF_INITIALIZED))
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("IDBCreateSessionImpl::CreateSession : Error not initialized\n"));
			*ppDBSession = NULL;
			return E_UNEXPECTED;
		}
		CComPolyObject<SessionClass> *pSession;

		// You can't QI for an interface other than IUnknown when aggregating
		// and creating the object.  You might ask for your own interface,
		// which would be bad.  Note, we return DB_E_NOAGGREGATION instead of
		// CLASS_E_NOAGGREGATION due to OLE DB constraints.
		if (pUnkOuter != NULL && !InlineIsEqualUnknown(riid))
			return DB_E_NOAGGREGATION;

		HRESULT hr = CComPolyObject<SessionClass>::CreateInstance(pUnkOuter, &pSession);
		if (SUCCEEDED(hr))
		{
			ATLASSUME(pSession != NULL);
			CComPtr<IObjectWithSite> spCreator;
			hr = pSession->QueryInterface(__uuidof(IObjectWithSite), (void**)&spCreator);
			if (SUCCEEDED(hr))
			{
				CComPtr<IUnknown> spOuterUnk;
				hr = pT->QueryInterface(__uuidof(IUnknown), (void**)&spOuterUnk);
				if (SUCCEEDED(hr))
				{
					hr = spCreator->SetSite(spOuterUnk);
					if (SUCCEEDED(hr))
					{
						hr = pSession->QueryInterface(riid, (void**)ppDBSession);
					}
				}
			}
			else
			{
				delete pSession;
			}
		}
		return hr;
	}
ATLPREFAST_UNSUPPRESS()
};

// IDBInitializeImpl
template <class T>
class ATL_NO_VTABLE IDBInitializeImpl :
	public IDBInitialize
{
public:
	IDBInitializeImpl()
	{
		m_dwStatus = 0;
		m_pCUtlPropInfo = NULL;
		m_cSessionsOpen = 0;
	}
	virtual ~IDBInitializeImpl()
	{
		delete m_pCUtlPropInfo;
	}

	STDMETHOD(Uninitialize)(void)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBInitializeImpl::Uninitialize\n"));
		T* pT = (T*)this;
		typename T::ObjectLock lock(pT);
		if (pT->m_cSessionsOpen != 0)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("Uninitialized called with Open Sessions\n"));
			return DB_E_OBJECTOPEN;
		}
		delete m_pCUtlPropInfo;
		m_pCUtlPropInfo = NULL;
		pT->m_dwStatus |= DSF_PERSIST_DIRTY;
		pT->m_dwStatus &= DSF_MASK_INIT;	// Clear all non-init flags.
		return S_OK;

	}

	DWORD m_dwStatus;
	CUtlPropInfo<T>* m_pCUtlPropInfo;
	LONG m_cSessionsOpen;

	STDMETHOD(Initialize)(void)
	{

		ATLTRACE(atlTraceDBProvider, 2, _T("IDBInitializeImpl::Initialize\n"));
		T *pT = (T*)(this);
		typename T::ObjectLock lock(pT);
		HRESULT hr;
		if (pT->m_dwStatus & DSF_INITIALIZED)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("IDBInitializeImpl::Initialize Error : Already Initialized\n"));
			return DB_E_ALREADYINITIALIZED;
		}
		delete m_pCUtlPropInfo;
		m_pCUtlPropInfo = NULL;
		m_pCUtlPropInfo = _ATL_NEW CUtlPropInfo<T>();
		if (m_pCUtlPropInfo == NULL)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("IDBInitializeImpl::Initialize Error : OOM\n"));
			return E_OUTOFMEMORY;
		}
		hr = m_pCUtlPropInfo->FInit();
		if (hr == S_OK)
		{
			pT->m_dwStatus |= DSF_INITIALIZED;
		}
		else
		{
			delete m_pCUtlPropInfo;
			m_pCUtlPropInfo = NULL;
		}
		return hr;
	}
};


// Implementation Class

class CPropColID :
	public PROPCOLID,
	public CDBIDOps
{
public:
	CPropColID()
	{
		VariantInit(&vValue);
	}
	~CPropColID()
	{
		FreeDBIDs(&dbidProperty);
		VariantClear(&vValue);
	}
	bool operator==(_In_ const CPropColID& colId)
	{
		return (CompareDBIDs(&dbidProperty, &(colId.dbidProperty)) == S_OK) ? true : false;
	}

};

class CColumnIds :
	public CDBIDOps,
	public CAtlArray<CPropColID>
{
public:
	PPROPCOLID AddNode()
	{
		CPropColID colID;
		_ATLTRY
		{
			Add(colID);
		}
		_ATLCATCH( e )
		{
			_ATLDELETEEXCEPTION( e )
			return NULL;
		}
		return &(GetAt(GetCount()-1));
	}
	HRESULT	RemoveColumnId(_In_ const DBID* pdbidProp)
	{
		for (size_t i = 0; i < GetCount(); i++)
		{
			if (CompareDBIDs(pdbidProp, &(GetAt(i).dbidProperty)) == S_OK)
			{
				if( i < 0 || i >= GetCount() )
					return E_FAIL;
				RemoveAt(i);
				return S_OK;
			}
		}

		return E_FAIL;
	}
	HRESULT	AddColumnId(_In_ DBPROP* pProp)
	{
		CPropColID colID;
		HRESULT hr = CopyDBIDs(&(colID.dbidProperty),&(pProp->colid));
		if(FAILED(hr))
			return hr;
		colID.dwOption = pProp->dwOptions;
		hr = VariantCopy(&(colID.vValue),&(pProp->vValue));
		if(FAILED(hr))
			return hr;
		//return (Add(colID)) ? S_OK : E_OUTOFMEMORY;
		_ATLTRY
		{
			Add(colID);
		}
		_ATLCATCH( e )
		{
			_ATLDELETEEXCEPTION( e );
			return E_OUTOFMEMORY;
		}
		return S_OK;
	}
	HRESULT	AddColumnId(_In_ PPROPCOLID pPropNode)
	{
		CPropColID colID;
		HRESULT hr = CopyDBIDs(&(colID.dbidProperty),&(pPropNode->dbidProperty));
		if(FAILED(hr))
			return hr;
		colID.dwOption = pPropNode->dwOption;
		hr = VariantCopy(&(colID.vValue),&(pPropNode->vValue));
		if(FAILED(hr))
			return hr;
		//return (Add(colID)) ? S_OK : E_OUTOFMEMORY;
		_ATLTRY
		{
			Add(colID);
		}
		_ATLCATCH( e )
		{
			_ATLDELETEEXCEPTION( e );
			return E_OUTOFMEMORY;
		}
		return S_OK;
	}
	ULONG GetCountOfPropColids()
	{
		return (ULONG)GetCount();
	}
	PPROPCOLID FindColumnId(_In_ const DBID* pdbidProp)
	{
		for (size_t i = 0; i < GetCount(); i++)
		{
			if (CompareDBIDs(pdbidProp, &(GetAt(i).dbidProperty)) == S_OK)
				return &(GetAt(i));
		}

		return NULL;
	}
	HRESULT GetValue(
		_In_ size_t iColId,
		_Out_ DWORD* pdwOptions,
		_Out_ DBID* pColid,
		_Inout_ VARIANT* pvValue)
	{
		ATLENSURE_RETURN(pdwOptions && pColid && pvValue);
		ATLASSERT(iColId >= 0 && iColId < GetCount());

		CPropColID& colId = GetAt(iColId);
		*pdwOptions = colId.dwOption;
		HRESULT hr = CopyDBIDs( pColid, &(colId.dbidProperty) );
		if(FAILED(hr) || hr == S_FALSE)
		{
			return hr;
		}
		if(FAILED(hr = VariantCopy(pvValue, &(colId.vValue))))
			return hr;
		return S_OK;
	}
};

const ULONG		cchDescBuffSize = 256;
const DWORD		DBINTERNFLAGS_CHANGED		= 0x00000001;
// Rules for GetPropertiesArgChk
const DWORD		ARGCHK_PROPERTIESINERROR	= 0x00000001;

// Implementation Class
template <class T>
class CUtlPropInfo :
	public CBitFieldOps,
	public CDBIDOps
{
public:
	enum EnumGetPropInfo
	{
		GETPROPINFO_ALLPROPIDS		= 0x0001,
		GETPROPINFO_NOTSUPPORTED	= 0x0002,
		GETPROPINFO_ERRORSOCCURRED	= 0x0004,
		GETPROPINFO_VALIDPROP		= 0x0008
	};

	CUtlPropInfo()
	{
		m_cUPropSet		 = 0;
		m_pUPropSet		 = NULL;
		m_cPropSetDex	= 0;
		m_cElemPerSupported = 0;
	}
	~CUtlPropInfo()
	{
		CoTaskMemFree(m_pUPropSet);
	}

	//Determine the number of description buffers needed
	ULONG CalcDescripBuffers(
		_In_ ULONG cPropInfoSet,
		_In_reads_(cPropInfoSet) DBPROPINFOSET* pPropInfoSet)
	{
		ULONG	cBuffers = 0;

		ATLASSUME(m_pUPropSet);
		ATLENSURE(cPropInfoSet && pPropInfoSet);

		for(ULONG ulSet=0; ulSet<cPropInfoSet; ulSet++)
		{
			if( GetPropertySetIndex(&(pPropInfoSet[ulSet].guidPropertySet)) == S_OK)
			{
				if( pPropInfoSet[ulSet].cPropertyInfos == 0 )
				{
					for(ULONG ul=0; ul<m_cPropSetDex; ul++)
					{
						cBuffers += m_pUPropSet[m_rgiPropSetDex[ul]].cUPropInfo;
					}
				}
				else
				{
					cBuffers += pPropInfoSet[ulSet].cPropertyInfos;
				}
			}
		}

		return cBuffers;
	}
	//Retrieve the property set indexes that match this property set.
	HRESULT	GetPropertySetIndex(_In_ const GUID* pPropertySet)
	{
		DWORD	dwFlag = 0;
		ULONG	ulSet;

		ATLASSUME(m_cUPropSet && m_pUPropSet);
		ATLASSUME(m_rgiPropSetDex);
		ATLASSERT(pPropertySet);

		m_cPropSetDex = 0;

		if(InlineIsEqualGUID(*pPropertySet, DBPROPSET_DATASOURCEALL))
		{
			dwFlag = DBPROPFLAGS_DATASOURCE;
		}
		else if(InlineIsEqualGUID(*pPropertySet, DBPROPSET_DATASOURCEINFOALL))
		{
			dwFlag = DBPROPFLAGS_DATASOURCEINFO;
		}
		else if(InlineIsEqualGUID(*pPropertySet, DBPROPSET_ROWSETALL))
		{
			dwFlag = DBPROPFLAGS_ROWSET;
		}
		else if(InlineIsEqualGUID(*pPropertySet,DBPROPSET_DBINITALL))
		{
			dwFlag = DBPROPFLAGS_DBINIT;
		}
		else if(InlineIsEqualGUID(*pPropertySet,DBPROPSET_SESSIONALL))
		{
			dwFlag = DBPROPFLAGS_SESSION;
		}
		else if(InlineIsEqualGUID(*pPropertySet,DBPROPSET_COLUMNALL))
		{
			dwFlag = DBPROPFLAGS_COLUMN;
		}
		else if(InlineIsEqualGUID(*pPropertySet,DBPROPSET_INDEXALL))
		{
			dwFlag = DBPROPFLAGS_INDEX;
		}
		else if(InlineIsEqualGUID(*pPropertySet,DBPROPSET_TABLEALL))
		{
			dwFlag = DBPROPFLAGS_TABLE;
		}
		else if(InlineIsEqualGUID(*pPropertySet,DBPROPSET_TRUSTEEALL))
		{
			dwFlag = DBPROPFLAGS_TRUSTEE;
		}
		else if(InlineIsEqualGUID(*pPropertySet,DBPROPSET_VIEWALL))
		{
			dwFlag = DBPROPFLAGS_VIEW;
		}
		else // No scan required, just look for match.
		{
			for(ulSet=0; ulSet<m_cUPropSet; ulSet++)
			{
				if( *(m_pUPropSet[ulSet].pPropSet) == *pPropertySet )
				{
					m_rgiPropSetDex[m_cPropSetDex] = ulSet;
					m_cPropSetDex++;
					break;
				}
			}
			goto EXIT;
		}

		// Scan through the property sets looking for matching attributes
		for(ulSet=0; ulSet<m_cUPropSet; ulSet++)
		{
			if( m_pUPropSet[ulSet].pUPropInfo[0].dwFlags & dwFlag )
			{
				m_rgiPropSetDex[m_cPropSetDex] = ulSet;
				m_cPropSetDex++;
			}
		}

	EXIT:
		return (m_cPropSetDex) ? S_OK : S_FALSE;

	}
	//Retrieve the property id pointer
	HRESULT	GetUPropInfoPtr(
		_In_ ULONG iPropSetDex,
		_In_ DBPROPID dwPropertyId,
		_Outptr_ UPROPINFO** ppUPropInfo)
	{
		// Scan through the property sets looking for matching attributes
		for(ULONG ulProps=0; ulProps<m_pUPropSet[iPropSetDex].cUPropInfo; ulProps++)
		{
			if( m_pUPropSet[iPropSetDex].pUPropInfo[ulProps].dwPropId == dwPropertyId )
			{
				*ppUPropInfo = &(m_pUPropSet[iPropSetDex].pUPropInfo[ulProps]);
				// Test to see if the property is supported for this
				// instantiation
				return (TESTBIT(&(m_rgdwSupported[iPropSetDex * m_cElemPerSupported]), ulProps)) ? S_OK : S_FALSE;
			}
		}
		return S_FALSE;
	}
	HRESULT	FInit(_In_ GUID* pguidSet = (GUID*)&GUID_NULL)
	{
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);

		HRESULT hr = InitAvailUPropSets(&m_cUPropSet, &m_pUPropSet, &m_cElemPerSupported, pguidSet);
		if (FAILED(hr))
			return hr;
		ATLASSERT((m_cUPropSet != 0) && (m_cElemPerSupported != 0));
		if(!m_cUPropSet || !m_cElemPerSupported)
			return E_FAIL;

		ATLTRY(m_rgdwSupported.Allocate(::ATL::AtlMultiplyThrow(m_cUPropSet, m_cElemPerSupported)));
		if(m_rgdwSupported == NULL)
		{
			m_cUPropSet=0;
			m_cElemPerSupported=0;
			return E_OUTOFMEMORY;
		}

		if(FAILED(hr = InitUPropSetsSupported()))
		{
			m_cUPropSet=0;
			m_cElemPerSupported=0;
			m_rgdwSupported.Free();
			return hr;
		}
		if(m_cUPropSet)
		{
			ATLTRY(m_rgiPropSetDex.Allocate(m_cUPropSet))
			if(m_rgiPropSetDex == NULL)
			{
				m_cUPropSet=0;
				m_cElemPerSupported=0;
				m_rgdwSupported.Free();
				return E_OUTOFMEMORY;
			}
		}
		return S_OK;
	}
	HRESULT	GetPropertyInfo(
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) const DBPROPIDSET rgPropertySets[],
		_Out_ ULONG* pcPropertyInfoSets,
		_Outptr_result_buffer_(*pcPropertyInfoSets) DBPROPINFOSET**	prgPropertyInfoSets,
		_Deref_opt_out_z_ WCHAR** ppDescBuffer,
		_In_ bool bInitialized = true,
		_In_opt_ const GUID* pGuid = NULL)
	{
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		HRESULT	hr = S_OK;
		ULONG ul, ulSet, ulNext, ulEnd;
		ULONG ulOutIndex;
		ULONG cSets;
		ULONG cPropInfos;
		//ULONG ulIndex = 0;
		ULONG cBuffers = 0;
		DWORD dwStatus = 0;
		DBPROPINFO*	pPropInfo = NULL;
		DBPROPINFO*	pCurPropInfo = NULL;
		WCHAR* pDescBuffer = NULL;
		WCHAR* pDescBufferEnd = NULL;
		DBPROPINFOSET* pPropInfoSet = NULL;
		UPROPINFO* pUPropInfo = NULL;
		WCHAR wszBuff[256];
		int	cch;
		CAtlArray<ULONG> rgInitPropsetIndexes;

		// If the consumer does not restrict the property sets
		// by specify an array of property sets and a cPropertySets
		// greater than 0, then we need to make sure we
		// have some to return
		if(cPropertySets == 0)
		{
			// Determine the number of property sets supported
			// In this case, it usually the enumerator or data source asking for
			// DBPROPSET_DBINIT information.

			if (pGuid != NULL)
			{
				// Need to determine if there are any UPROPSET_USERINIT sets here
				// they should be added to DBINIT.
				ULONG ulUserInitSets = 0;
				for (ULONG l=0; l<m_cUPropSet; l++)
					if (m_pUPropSet[l].dwFlags & UPROPSET_USERINIT)
						ulUserInitSets++;

				cSets = 1 + ulUserInitSets;		// one for DBINIT which is required
			}
			else
			{
				cSets = m_cUPropSet;
			}
		}
		else
		{
			cSets = 0;

			// Determine number of property sets required
			// This is only required when any of the "special" property set GUIDs were specified
			for(ulSet=0; ulSet<cPropertySets; ulSet++)
			{
				if (GetPropertySetIndex(&(rgPropertySets[ulSet].guidPropertySet)) == S_OK)
					cSets += m_cPropSetDex;
				else
					cSets++;
			}
		}
		ATLASSERT(cSets);

		// Allocate the DBPROPINFOSET structures
		pPropInfoSet = (DBPROPINFOSET*)::ATL::AtlCoTaskMemCAlloc(cSets, static_cast<ULONG>(sizeof(DBPROPINFOSET)));
		if(pPropInfoSet == NULL)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("Could not allocate DBPROPSET array for GetProperties\n"));
			hr =  E_OUTOFMEMORY;
			goto EXIT;
		}

		memset(pPropInfoSet, 0, cSets * sizeof(DBPROPINFOSET));

		ulOutIndex = 0;
		ULONG ulTempPropsetIndex = 0;
		ulEnd = cPropertySets == 0 ? cSets : cPropertySets;
		// Fill in the output array
		for(ulSet=0; ulSet<ulEnd; ulSet++)
		{
 			// Depending of if Property sets are specified store the
			// return property set.
			if (cPropertySets == 0)
			{
				if (pGuid != NULL)
				{
					// Need to change this: set the guidPropertySet to the matching
					// initialization property group (not DBINITALL).
					for (ULONG ulCurrentInitSet = ulTempPropsetIndex; ulCurrentInitSet < m_cUPropSet; ulCurrentInitSet++)
					{
						// We need to locate either the DBINIT or USERINIT property sets here
						// and set the property set index up correctly.
						if (InlineIsEqualGUID(*(m_pUPropSet[ulCurrentInitSet].pPropSet), DBPROPSET_DBINIT) ||
							(m_pUPropSet[ulCurrentInitSet].dwFlags & UPROPSET_USERINIT))
						{
							rgInitPropsetIndexes.Add(ulCurrentInitSet);
							ulTempPropsetIndex = ulCurrentInitSet + 1;
							//ulIndex = ulCurrentInitSet;
							//pPropInfoSet[ulSet].guidPropertySet = *pGuid;
							pPropInfoSet[ulSet].guidPropertySet = *(m_pUPropSet[ulCurrentInitSet].pPropSet);
							GetPropertySetIndex(&pPropInfoSet[ulSet].guidPropertySet);	// Need to set the m_cPropSetDex variable
							break;
						}
					}
					//if (ulCurrentInitSet == m_cUPropSet)
					//{
					//	ulIndex = 0;
					//}

				}
				else
				{
					pPropInfoSet[ulSet].guidPropertySet = *(m_pUPropSet[ulSet].pPropSet);
				}
			}
			else
			{
				GUID const& guidSet = rgPropertySets[ulSet].guidPropertySet;
				if( (InlineIsEqualGUID(guidSet, DBPROPSET_DATASOURCEALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_DATASOURCEINFOALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_DBINITALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_SESSIONALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_COLUMNALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_CONSTRAINTALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_INDEXALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_TABLEALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_TRUSTEEALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_VIEWALL) ||
					InlineIsEqualGUID(guidSet, DBPROPSET_ROWSETALL)) &&
					GetPropertySetIndex(&guidSet) == S_OK )
				{
					for(ul=0; ul<m_cPropSetDex; ul++,ulOutIndex++)
					{
						pPropInfoSet[ulOutIndex].guidPropertySet	= *(m_pUPropSet[m_rgiPropSetDex[ul]].pPropSet);
						pPropInfoSet[ulOutIndex].cPropertyInfos		= 0;
					}
				}
				else
				{
					// Handle non-category property sets
					// Handle unknown property sets
					pPropInfoSet[ulOutIndex].guidPropertySet = guidSet;
					pPropInfoSet[ulOutIndex].cPropertyInfos	 = rgPropertySets[ulSet].cPropertyIDs;
					ulOutIndex++;
				}
			}
		}

		// Allocate a Description Buffer if needed
		if( ppDescBuffer )
		{
			cBuffers = CalcDescripBuffers(cSets, pPropInfoSet);
			if( cBuffers != 0 )
			{
				const SIZE_T uDescBuffersElementSize = cBuffers * cchDescBuffSize;
				const SIZE_T uDescBuffersByteSize = uDescBuffersElementSize * sizeof(WCHAR);
				if (uDescBuffersByteSize / (cchDescBuffSize * sizeof(WCHAR)) != cBuffers)
				{
					hr = E_FAIL;
					goto EXIT;
				}
				pDescBuffer = (WCHAR*)CoTaskMemAlloc(uDescBuffersByteSize);
				if(pDescBuffer == NULL)
				{
					hr = E_OUTOFMEMORY;
					goto EXIT;
				}
				*ppDescBuffer = pDescBuffer;
				pDescBufferEnd = pDescBuffer + uDescBuffersElementSize;
				wmemset(pDescBuffer, 0, uDescBuffersElementSize);
			}
		}

		// Process requested or derived Property sets
		dwStatus = 0;
		for(ulSet=0; ulSet<cSets; ulSet++)
		{
			ulNext=0;
			cPropInfos = 0;
			pPropInfo = NULL;
			dwStatus &= (GETPROPINFO_ERRORSOCCURRED | GETPROPINFO_VALIDPROP);

			// Calculate the number of property nodes needed for this
			// property set.
			if( cPropertySets == 0 )
			{
				ULONG ulTempSet;
				if (pGuid != NULL)
				{
					ATLASSERT( ulSet < rgInitPropsetIndexes.GetCount() );
					ulTempSet = rgInitPropsetIndexes[ulSet]; // ulIndex;
				}
				else
					ulTempSet = ulSet;

				cPropInfos = m_pUPropSet[ulTempSet].cUPropInfo;
				dwStatus |= GETPROPINFO_ALLPROPIDS;
				m_rgiPropSetDex[0] = ulTempSet;
				m_cPropSetDex = 1;
				_ATLDUMPPROPSETIID(*m_pUPropSet[ulTempSet].pPropSet, dwStatus);
			}
			else
			{
				// If the count of PROPIDs is 0 (NOTE: the above routine already determined
				// if it belonged to a category and if so set the count of properties to 0 for
				// each propset in that category.
				if( pPropInfoSet[ulSet].cPropertyInfos == 0 )
				{
					dwStatus |= GETPROPINFO_ALLPROPIDS;
					// We have to determine if the property set is supported and if so
					// the count of properties in the set.
					if( (GetPropertySetIndex(&(pPropInfoSet[ulSet].guidPropertySet)) == S_FALSE)
						|| (!bInitialized &&
						!(InlineIsEqualGUID(pPropInfoSet[ulSet].guidPropertySet, DBPROPSET_DBINIT)) &&
						!(InlineIsEqualGUID(pPropInfoSet[ulSet].guidPropertySet, DBPROPSET_DBINITALL))))
					{
						dwStatus |= GETPROPINFO_NOTSUPPORTED;
						dwStatus |= GETPROPINFO_ERRORSOCCURRED;
						_ATLDUMPPROPSETIID(pPropInfoSet[ulSet].guidPropertySet, dwStatus);
						goto NEXT_SET;
					}
					else
					{
						ATLASSUME( m_cPropSetDex == 1 );
						cPropInfos += m_pUPropSet[m_rgiPropSetDex[0]].cUPropInfo;
						_ATLDUMPPROPSETIID(pPropInfoSet[ulSet].guidPropertySet, dwStatus);
					}
				}
				else
				{
					// We also handle the case here where the user has requested
					// a non-initialization group property info set while the
					// provider is not initialized.  In this case, properties should
					// not be set.
					cPropInfos = pPropInfoSet[ulSet].cPropertyInfos;
					if( (GetPropertySetIndex(&(pPropInfoSet[ulSet].guidPropertySet)) == S_FALSE)
						|| (!bInitialized &&
						!(InlineIsEqualGUID(pPropInfoSet[ulSet].guidPropertySet, DBPROPSET_DBINIT)) &&
						!(InlineIsEqualGUID(pPropInfoSet[ulSet].guidPropertySet, DBPROPSET_DBINITALL))))
					{
						dwStatus |= GETPROPINFO_NOTSUPPORTED;
						dwStatus |= GETPROPINFO_ERRORSOCCURRED;
						_ATLDUMPPROPSETIID(pPropInfoSet[ulSet].guidPropertySet, dwStatus);
					}
				}
			}

			// Allocate DBPROP array
			ATLASSERT( cPropInfos != 0 );
			pPropInfo = (DBPROPINFO*)::ATL::AtlCoTaskMemCAlloc(cPropInfos, static_cast<ULONG>(sizeof(DBPROPINFO)));
			if( pPropInfo )
			{
				// Initialize Buffer
				memset(pPropInfo, 0, cPropInfos * sizeof(DBPROPINFO));
				for(ULONG ulProp=0; ulProp<cPropInfos; ulProp++)
				{
					VariantInit(&(pPropInfo[ulProp].vValues));
					if( dwStatus & GETPROPINFO_NOTSUPPORTED )
					{
						// Not supported, thus we need to mark all as NOT_SUPPORTED
						pPropInfo[ulProp].dwPropertyID = rgPropertySets[ulSet].rgPropertyIDs[ulProp];
						pPropInfo[ulProp].dwFlags = DBPROPFLAGS_NOTSUPPORTED;
						dwStatus |= GETPROPINFO_ERRORSOCCURRED;
						_ATLDUMPPROPERTY(pPropInfo[ulProp].dwPropertyID, pPropInfo[ulProp].dwFlags);
					}
				}
				// Make sure we support the property set
				if( dwStatus & GETPROPINFO_NOTSUPPORTED )
				{
					ulNext = cPropInfos;
					goto NEXT_SET;
				}

				// Retrieve the property information for this property set
				for(ul=0; ul<m_cPropSetDex; ul++)
				{
					pUPropInfo = (m_pUPropSet[m_rgiPropSetDex[ul]].pUPropInfo);
					ATLENSURE_RETURN( pUPropInfo );

					// Retrieve current value of properties
					if( dwStatus & GETPROPINFO_ALLPROPIDS )
					{
						for(ULONG ulProp=0; ulProp<m_pUPropSet[m_rgiPropSetDex[ul]].cUPropInfo; ulProp++)
						{
							// Verify property is supported, if not do not return
							if( !TESTBIT(&(m_rgdwSupported[m_rgiPropSetDex[ul] * m_cElemPerSupported]), ulProp) )
								continue;

							pCurPropInfo = &(pPropInfo[ulNext]);

							// If the ppDescBuffer pointer was not NULL, then
							// we need supply description of the properties
							if( ppDescBuffer )
							{
								// Set Buffer pointer
								pCurPropInfo->pwszDescription = pDescBuffer;

								// Load the string into temp buffer
								cch = LoadDescription(pUPropInfo[ulProp].ulIDS, wszBuff, (sizeof(wszBuff)/sizeof(*wszBuff)));
								if( cch )
								{
									// Adjust for '\0'
									cch++;

									// Transfer to official buffer if room
									if (pDescBuffer > pDescBufferEnd)
									{
										return E_FAIL;
									}
									Checked::memcpy_s(pDescBuffer, (pDescBufferEnd-pDescBuffer)*sizeof(WCHAR), wszBuff, cch * sizeof(WCHAR));
									pDescBuffer += cch;
								}
								else
								{
									if(pDescBuffer > pDescBufferEnd)
									{
										return E_FAIL;
									}
									Checked::wcscpy_s(pDescBuffer, pDescBufferEnd-pDescBuffer, L"UNKNOWN");
									pDescBuffer += (wcslen(L"UNKNOWN") + 1);
									_ATLDUMPPROPERTY(pCurPropInfo->dwPropertyID, ATLDB_NO_STRING);
								}
							}

							pCurPropInfo->dwPropertyID = pUPropInfo[ulProp].dwPropId;

							// Strip out any user defined flags that may be around.  Note,
							// this isn't a full-proof thing because properties change.  It
							// won't work in OLE DB 2.5 if someone does a property like 0x40000
							DWORD dwFlags = pUPropInfo[ulProp].dwFlags & 0xfffff;

							pCurPropInfo->dwFlags = dwFlags;
							pCurPropInfo->vtType = pUPropInfo[ulProp].VarType;
							pCurPropInfo->vValues.vt = VT_EMPTY;

							dwStatus |= GETPROPINFO_VALIDPROP;
							// Increment to next available buffer
							ulNext++;
							_ATLDUMPPROPERTY(pCurPropInfo->dwPropertyID, pCurPropInfo->dwFlags);
						}
					}
					else
					{
						ATLASSUME( m_cPropSetDex == 1 );
						ULONG cIterations = ((cPropInfos>cBuffers) && (ppDescBuffer)) ? cBuffers : cPropInfos;

						for( ULONG ulProp = 0; ulProp < cIterations; ulProp++, ulNext++ )
						{
							pCurPropInfo = &(pPropInfo[ulNext]);

							// Process Properties based on Restriction array.
							pCurPropInfo->dwPropertyID = rgPropertySets[ulSet].rgPropertyIDs[ulProp];

							if( GetUPropInfoPtr(m_rgiPropSetDex[ul], pCurPropInfo->dwPropertyID, &pUPropInfo)
								== S_OK )
							{
								// If the ppDescBuffer pointer was not NULL, then
								// we need supply description of the properties
								if( ppDescBuffer )
								{
									// Set Buffer pointer
									pCurPropInfo->pwszDescription = pDescBuffer;

									// Load the string into temp buffer
									cch = LoadDescription(pUPropInfo->ulIDS, wszBuff, (sizeof(wszBuff)/sizeof(*wszBuff)));
									if( cch )
									{
										// Adjust for '\0'
										cch++;

										// Transfer to official buffer if room
										if(pDescBuffer > pDescBufferEnd)
											return E_FAIL;
										Checked::memcpy_s(pDescBuffer, (pDescBufferEnd-pDescBuffer)*sizeof(WCHAR), wszBuff, cch * sizeof(WCHAR));
										pDescBuffer += cch;
									}
									else
									{
										if(pDescBuffer > pDescBufferEnd)
										{
											return E_FAIL;
										}
										Checked::wcscpy_s(pDescBuffer, pDescBufferEnd-pDescBuffer, L"UNKNOWN");
										pDescBuffer += (wcslen(L"UNKNOWN") + 1);
										_ATLDUMPPROPERTY(pCurPropInfo->dwPropertyID, ATLDB_NO_STRING);
									}
								}

								pCurPropInfo->dwPropertyID = pUPropInfo->dwPropId;

								// Strip out any user defined flags that may be around.  Note,
								// this isn't a full-proof thing because properties change.  It
								// won't work in OLE DB 2.5 if someone does a property like 0x40000
								DWORD dwFlags = pUPropInfo->dwFlags & 0xfffff;

								pCurPropInfo->dwFlags = dwFlags;
								pCurPropInfo->vtType = pUPropInfo->VarType;

								dwStatus |= GETPROPINFO_VALIDPROP;
							}
							else
							{
								// Not Supported
								pCurPropInfo->dwFlags = DBPROPFLAGS_NOTSUPPORTED;
								dwStatus |= GETPROPINFO_ERRORSOCCURRED;
							}
							_ATLDUMPPROPERTY(pCurPropInfo->dwPropertyID, pCurPropInfo->dwFlags);
						}
					}
				}
			}
			else
			{
				hr = E_OUTOFMEMORY;
				goto EXIT;
			}

NEXT_SET:
			pPropInfoSet[ulSet].cPropertyInfos = ulNext;
			pPropInfoSet[ulSet].rgPropertyInfos = pPropInfo;
		}

		// Success, set return values
		*pcPropertyInfoSets = cSets;
		*prgPropertyInfoSets = pPropInfoSet;

		// At least one propid was marked as not S_OK
		if( dwStatus & GETPROPINFO_ERRORSOCCURRED )
		{
			// If at least 1 property was set
			if( dwStatus & GETPROPINFO_VALIDPROP )
				return DB_S_ERRORSOCCURRED;
			else
			{
				// Do not free any of the rgPropertyInfoSets, but
				// do free the ppDescBuffer
				if( pDescBuffer )
				{
					ATLASSERT( ppDescBuffer );
					CoTaskMemFree(pDescBuffer);
					*ppDescBuffer = NULL;
				}
				return DB_E_ERRORSOCCURRED;
			}
		}

		return S_OK;
EXIT:
		// Check if failure and clean up any allocated memory
		if( FAILED(hr) &&
			(hr != DB_E_ERRORSOCCURRED) )
		{
			// Free Description Buffer
			if( pDescBuffer )
			{
				ATLASSERT( ppDescBuffer );

				CoTaskMemFree(pDescBuffer);
				*ppDescBuffer = NULL;
			}

			if( pPropInfoSet )
			{
				// Loop through Property Sets
				for(ulSet=0; ulSet<cSets; ulSet++)
					CoTaskMemFree(pPropInfoSet[ulSet].rgPropertyInfos);
				CoTaskMemFree(pPropInfoSet);
			}
		}

		return hr;
	}

	CComAutoCriticalSection m_oCriticalSection; // critical section to synchronize access to the class
	ULONG m_cUPropSet; //count of UPropSet items
	UPROPSET* m_pUPropSet; //Pointer to UPropset items
	ULONG m_cPropSetDex; 	//count of UPropSet Indexes
	CAutoVectorPtr<ULONG> m_rgiPropSetDex;//array of UPropSet Index values
	ULONG m_cElemPerSupported; //number of DWORDS per UPropSet to indicate supported UPropIds
	CAutoVectorPtr<DWORD> m_rgdwSupported;//array of DWORDs indicating supported UPropIds

	enum EnumUPropSetFlags
	{
		UPROPSET_HIDDEN				= 0x1,
		UPROPSET_PASSTHROUGH		= 0x2,
		UPROPSET_USERINIT			= 0x4
	};

	HRESULT	InitAvailUPropSets(
		_Out_ ULONG* pcUPropSet,
		_Outptr_result_buffer_maybenull_(*pcUPropSet) UPROPSET** ppUPropSet,
		_Out_ ULONG* pcElemPerSupported,
		_Out_opt_ GUID* pguid)
	{
		ATLENSURE_RETURN(pcUPropSet && ppUPropSet);
		CoTaskMemFree(*ppUPropSet);
		*ppUPropSet = NULL;
		int cSets = (int)(ULONG_PTR)T::_GetPropSet(NULL, pcElemPerSupported);
		UPROPSET* pSet = (UPROPSET*)::ATL::AtlCoTaskMemCAlloc(static_cast<ULONG>(sizeof(UPROPSET)), cSets);
		if (pSet == NULL)
		{
			*pcUPropSet=0;
			*ppUPropSet=NULL;
			*pcElemPerSupported=0;
			return E_OUTOFMEMORY;
		}
		*ppUPropSet = T::_GetPropSet(pcUPropSet, pcElemPerSupported, pSet, pguid);
		return S_OK;
	}
	OUT_OF_LINE HRESULT	InitUPropSetsSupported()
	{
		ULONG cPropSet = 0, cElemsPerSupported = 0;
		int cSets = (int)(ULONG_PTR)T::_GetPropSet(NULL, &cElemsPerSupported);
		UPROPSET* pSet = (UPROPSET*)::ATL::AtlCoTaskMemCAlloc(static_cast<ULONG>(sizeof(UPROPSET)), cSets);
		if (pSet == NULL)
			return E_OUTOFMEMORY;
		pSet = T::_GetPropSet(&cPropSet, &cElemsPerSupported, pSet);
		memset(m_rgdwSupported, 0xFFFF, cPropSet * cElemsPerSupported * sizeof(DWORD));
		CoTaskMemFree(pSet);
		return S_OK;
	}
	//Load a localized description
	int	LoadDescription(
		_In_ ULONG ids,
		_Out_writes_to_(cchBuff, return + 1) PWSTR pwszBuff,
		_In_ ULONG cchBuff)
	{
		USES_CONVERSION_EX;
		CTempBuffer<TCHAR> tmpBuffer;
		TCHAR* pszBuf = tmpBuffer.Allocate(cchBuff);
		if (pszBuf == NULL)
			return 0;
		int nTemp = LoadString(_AtlBaseModule.GetResourceInstance(), ids, pszBuf, cchBuff);
		if (nTemp != 0)
		{
			Checked::wcscpy_s(pwszBuff, cchBuff, SAL_Assume_notnull_for_opt_z_(T2W_EX_DEF(pszBuf)));
		}
		return nTemp;
	}
};

class ATL_NO_VTABLE CUtlPropsBase :
	public CBitFieldOps,
	public CDBIDOps
{
public:

	virtual ~CUtlPropsBase()
	{
	}

	CComAutoCriticalSection m_oCriticalSection; // critical section to synchronize access to the class
	ULONG m_cUPropSet; //count of UPropSet items
	UPROPSET* m_pUPropSet; //Pointer to UPropset items
	CAutoVectorPtr< UPROP > m_pUProp;
	ULONG m_cUPropSetHidden; //Count of Hidden items
	DWORD m_dwFlags; //Configuration flags
	ULONG m_cPropSetDex; //count of UPropSet Indexes
	CAutoVectorPtr< ULONG > m_rgiPropSetDex; //pointer to Array of UPropSet Index values
	ULONG m_cElemPerSupported;//number of DWORDS per UPropSet to indicate supported UPropIds
	CAutoVectorPtr< DWORD > m_rgdwSupported; //pointer to array of DWORDs indicating supported UPropIds
	CAutoVectorPtr< DWORD > m_rgdwPropsInError;//pointer to array of DWORDs indicating if property is in error

	enum EnumUPropSetFlags
	{
		UPROPSET_HIDDEN				= 0x1,
		UPROPSET_PASSTHROUGH		= 0x2,
		UPROPSET_USERINIT			= 0x4
	};
	enum EnumGetProp
	{
		GETPROP_ALLPROPIDS			= 0x0001,
		GETPROP_NOTSUPPORTED		= 0x0002,
		GETPROP_ERRORSOCCURRED		= 0x0004,
		GETPROP_VALIDPROP			= 0x0008,
		GETPROP_PROPSINERROR		= 0x0010
	};

	enum EnumSetProp
	{
		SETPROP_BADOPTION			= 0x0001,
		SETPROP_NOTSUPPORTED		= 0x0002,
		SETPROP_VALIDPROP			= 0x0004,
		SETPROP_ERRORS				= 0x0008,
		SETPROP_COLUMN_LEVEL		= 0x0010,
		SETPROP_WAS_REQUIRED		= 0x0020
	};

	HRESULT	SetPassThrough(_In_ const DBPROPSET* pPropSet)
	{
		ATLENSURE_RETURN(pPropSet);

		DBPROP*	pProp = pPropSet->rgProperties;

		//Default implementation just sets all properties as NOTSUPPORTED
		for( ULONG ul=0; ul<pPropSet->cProperties; ul++, pProp++ )
			pProp->dwStatus = DBPROPSTATUS_NOTSUPPORTED;

		return DB_E_ERRORSOCCURRED;
	}

	_Post_satisfies_(return == S_OK || return == S_FALSE)
	_Success_(return == S_OK)
	HRESULT	GetIndexofPropIdinPropSet(
		_In_ ULONG iCurSet,
		_In_ DBPROPID dwPropertyId,
		_Out_ ULONG* piCurPropId)
	{
		ATLENSURE_RETURN(piCurPropId);
		UPROPINFO* pUPropInfo = m_pUPropSet[iCurSet].pUPropInfo;
		for(ULONG ul=0; ul<m_pUPropSet[iCurSet].cUPropInfo; ul++)
		{
			if( dwPropertyId == pUPropInfo[ul].dwPropId )
			{
				*piCurPropId = ul;
				// Test to see if the property is supported for this
				// instantiation
				return (TESTBIT(&(m_rgdwSupported[iCurSet * m_cElemPerSupported]), ul)) ? S_OK : S_FALSE;
			}
		}

		return S_FALSE;
	}

	virtual HRESULT	IsValidValue(
		_In_ ULONG /*iCurSet*/,
		_In_ DBPROP* pDBProp)
	{
		ATLENSURE_RETURN(pDBProp != NULL);
		CComVariant var = pDBProp->vValue;
		if (var.vt == VT_BOOL)
		{
			if (var.boolVal != ATL_VARIANT_TRUE && var.boolVal != ATL_VARIANT_FALSE)
				return S_FALSE;
		}

		return S_OK;
	}

	virtual HRESULT OnInterfaceRequested(_In_ REFIID riid)
	{
		// This function exists as part of the change in the OLE DB spec. If
		// a consumer opens an object and requests an optional interface, the
		// provider should automatically set the property representing that
		// interface to true.
		CDBPropSet propset(DBPROPSET_ROWSET);
		const GUID* ppGuid[1];
		ppGuid[0] = &DBPROPSET_ROWSET;

		if (InlineIsEqualGUID(riid, __uuidof(IRowsetChange)))
		{
			if(!propset.AddProperty(DBPROP_IRowsetChange, true))
			{
				return E_FAIL;
			}
		}
		else if (InlineIsEqualGUID(riid, __uuidof(IRowsetUpdate)))
		{
			if(!propset.AddProperty(DBPROP_IRowsetUpdate, true))
			{
				return E_FAIL;
			}
		}
		else if (InlineIsEqualGUID(riid, __uuidof(IRowsetLocate)))
		{
			if(!propset.AddProperty(DBPROP_IRowsetLocate, true))
			{
				return E_FAIL;
			}
		}
		else if (InlineIsEqualGUID(riid, __uuidof(IConnectionPointContainer)))
		{
			if(!propset.AddProperty(DBPROP_IConnectionPointContainer, true))
			{
				return E_FAIL;
			}
		}
		else if (InlineIsEqualGUID(riid, __uuidof(IRowsetScroll)))
		{
			if(!propset.AddProperty(DBPROP_IRowsetScroll, true))
			{
				return E_FAIL;
			}
		}

		if (propset.cProperties > 0)
			return SetProperties(0, 1, &propset, 1, ppGuid);

		return S_OK;
	}

	virtual HRESULT OnPropertyChanged(
		_In_ ULONG /*iCurSet*/,
		_In_ DBPROP* /*pDBProp*/) = 0;

ATLPREFAST_SUPPRESS(6014)
	/* prefast noise VSW 498981 */
	HRESULT	SetProperty(
		_In_ ULONG iCurSet,
		_In_ ULONG iCurProp,
		_Inout_ DBPROP* pDBProp)
	{
		HRESULT	hr = S_OK;
		UPROP* pUProp;
		UPROPVAL* pUPropVal;
		UPROPINFO* pUPropInfo;
		ULONG iUProp;

		ATLENSURE_RETURN( pDBProp );

		// Set pointer to correct set
		pUProp = &(m_pUProp[iCurSet]);
		ATLENSURE_RETURN( pUProp );

		pUPropInfo = &(m_pUPropSet[iCurSet].pUPropInfo[iCurProp]);
		ATLENSURE_RETURN( pUPropInfo );

		// Determine the index within m_pUProp
		for(iUProp=0; iUProp<pUProp->cPropIds; iUProp++)
		{
			if( (pUProp->rgpUPropInfo[iUProp])->dwPropId == pDBProp->dwPropertyID )
				break;
		}

		if( iUProp >= pUProp->cPropIds )
		{
			ATLASSERT( !"Should have found index of property to set" );
			hr = E_FAIL;
			pDBProp->dwStatus = DBPROPSTATUS_NOTSUPPORTED;
			goto EXIT;
		}

		//Get the UPROPVAL node pointer within that propset.
		pUPropVal = &(pUProp->pUPropVal[iUProp]);
		ATLENSURE_RETURN( pUPropVal );

		// Handle VT_EMPTY, which indicates to the provider to
		// reset this property to the providers default
		if( pDBProp->vValue.vt == VT_EMPTY )
		{
			if( pUPropInfo->dwFlags & DBPROPFLAGS_COLUMNOK )
			{
				// Remove any nodes, because the default applies to
				// all columns
				delete pUPropVal->pCColumnIds;
				pUPropVal->pCColumnIds = NULL;
			}

			// Should clear here, since previous values may already
			// have been cached and need to be replaced.
			VariantClear(&(pUPropVal->vValue));

			pUPropVal->dwFlags &= ~DBINTERNFLAGS_CHANGED;
			hr = GetDefaultValue(iCurSet, pDBProp->dwPropertyID,
				&(pUPropVal->dwOption), &(pUPropVal->vValue));

			goto EXIT;
		}

		// Column Level
		if( pUPropInfo->dwFlags & DBPROPFLAGS_COLUMNOK )
		{
			// Check to see if it applies to all
			if( (CompareDBIDs(&(pDBProp->colid), &DB_NULLID) == S_OK) )
			{
				// Remove the Columns Storage object
				delete pUPropVal->pCColumnIds;
				pUPropVal->pCColumnIds = NULL;
				pUPropVal->dwOption = pDBProp->dwOptions;
				if( FAILED(hr = VariantCopy(&(pUPropVal->vValue),
					&(pDBProp->vValue))) )
					goto EXIT;
				pUPropVal->dwFlags |= DBINTERNFLAGS_CHANGED;
			}
			else // Does not apply to all columns
			{
				if( pUPropVal->pCColumnIds == NULL )
				{
					pUPropVal->pCColumnIds = _ATL_NEW CColumnIds;
				}

				if( pUPropVal->pCColumnIds )
				{
					if( FAILED(hr = (pUPropVal->pCColumnIds)->AddColumnId(pDBProp)) )
						goto EXIT;
					pUPropVal->dwFlags |= DBINTERNFLAGS_CHANGED;
				}
				else
				{
					hr = E_OUTOFMEMORY;
					goto EXIT;
				}
			}
		}
		else
		{
			// Set for non-column level properties
			pUPropVal->dwOption = pDBProp->dwOptions;

			// Our provider has no limit on the maximum number of rows
			// that can have pending changes, therefore the value of the
			// DBPROP_MAXPENDINGROWS property will always be zero (default),
			// regardless of what the user attempts to set it to.
			// In the code below, we modify the property value only if
			// this is not the DBPROP_MAXPENDINGROWS property.
			if( pDBProp->dwPropertyID != DBPROP_MAXPENDINGROWS )
			{
				if( FAILED(hr = VariantCopy(&(pUPropVal->vValue),
					&(pDBProp->vValue))) )
					goto EXIT;
			}
			if( FAILED(hr = OnPropertyChanged(iCurSet, pDBProp)))
				goto EXIT;
			pUPropVal->dwFlags |= DBINTERNFLAGS_CHANGED;
		}

EXIT:
		if( hr == S_OK )
			pDBProp->dwStatus = DBPROPSTATUS_OK;

		return hr;
	}
ATLPREFAST_UNSUPPRESS()

ATLPREFAST_SUPPRESS(6102)
	HRESULT	SetProperties(
		_In_ const DWORD /*dwStatus*/,
		_In_ const ULONG cPropertySets,
		_In_reads_(cPropertySets) const DBPROPSET rgPropertySets[],
		_In_ const ULONG cSelectProps = 1,
		_In_reads_(cSelectProps) const GUID* const ppGuid[] = NULL,
		_In_ bool bIsCreating = false)
	{
		DWORD dwState = 0;
		ULONG ulCurSet, ulCurProp, ulProp;
		DBPROP*	rgDBProp;
		UPROPINFO* pUPropInfo;
		CComVariant vDefaultValue;
		DWORD dwOption;

		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);

		// ppGuid specifies the property sets that the consumer can set based
		// on the interface that called this function.
		ATLENSURE_RETURN(ppGuid != NULL);

		if ((cPropertySets != 0) && (rgPropertySets == NULL))
			return E_INVALIDARG;

		// Process property sets
		for(ULONG ulSet=0; ulSet<cPropertySets; ulSet++)
		{
			if ((rgPropertySets[ulSet].cProperties != 0) && (rgPropertySets[ulSet].rgProperties == NULL))
				return E_INVALIDARG;

			bool bAvailable = false;
			for (ULONG l=0; l<cSelectProps; l++)
			{
				if (InlineIsEqualGUID(*ppGuid[l], rgPropertySets[ulSet].guidPropertySet))
					bAvailable |= true;
			}

			// Make sure we support the property set
			if( !bAvailable ||
				(GetIndexofPropSet(&(rgPropertySets[ulSet].guidPropertySet), &ulCurSet) == S_FALSE ))
			{
				// Not supported, thus we need to mark all as NOT_SUPPORTED
				rgDBProp = rgPropertySets[ulSet].rgProperties;
				for(ulProp=0; ulProp<rgPropertySets[ulSet].cProperties; ulProp++)
				{
					dwState |= SETPROP_ERRORS;
					dwState |= (rgDBProp[ulProp].dwOptions == DBPROPOPTIONS_REQUIRED) ? SETPROP_WAS_REQUIRED : 0;
					rgDBProp[ulProp].dwStatus = DBPROPSTATUS_NOTSUPPORTED;
					_ATLDUMPPROPSETIID(rgPropertySets[ulSet].guidPropertySet, dwState);
				}
				continue;
			}

			// Handle property sets marked as pass through
			if( m_pUPropSet[ulCurSet].dwFlags & UPROPSET_PASSTHROUGH )
			{
				HRESULT hr = SetPassThrough(&rgPropertySets[ulSet]);
				if( hr == DB_E_ERRORSOCCURRED )
				{
					dwState |= SETPROP_ERRORS;
					dwState |= SETPROP_WAS_REQUIRED;
				}
				else if( hr == DB_S_ERRORSOCCURRED )
				{
					dwState |= SETPROP_ERRORS;
					dwState |= SETPROP_VALIDPROP;
				}
				else
				{
					ATLASSERT( hr == S_OK );
					dwState |= SETPROP_VALIDPROP;
				}

				continue;
			}

			// Handle properties of a supported property set
			rgDBProp = rgPropertySets[ulSet].rgProperties;
			for(ulProp=0; ulProp<rgPropertySets[ulSet].cProperties; ulProp++)
			{
				// Is this a supported PROPID for this property set
				if( GetIndexofPropIdinPropSet(ulCurSet, rgDBProp[ulProp].dwPropertyID,
					&ulCurProp) == S_FALSE)
				{
					dwState |= SETPROP_ERRORS;
					dwState |= (rgDBProp[ulProp].dwOptions == DBPROPOPTIONS_REQUIRED) ? SETPROP_WAS_REQUIRED : 0;
					rgDBProp[ulProp].dwStatus = DBPROPSTATUS_NOTSUPPORTED;
					_ATLDUMPPROPERTY(rgDBProp[ulProp].dwPropertyID, rgDBProp[ulProp].dwStatus);
					continue;
				}

				// Set the pUPropInfo pointer
				pUPropInfo = &(m_pUPropSet[ulCurSet].pUPropInfo[ulCurProp]);
				ATLENSURE_RETURN( pUPropInfo );

				// check dwOption for a valid option
				if( (rgDBProp[ulProp].dwOptions != DBPROPOPTIONS_REQUIRED)  &&
					(rgDBProp[ulProp].dwOptions != DBPROPOPTIONS_SETIFCHEAP) )
				{
					ATLTRACE(atlTraceDBProvider, 0, _T("SetProperties dwOptions Invalid: %u\n"), rgDBProp[ulProp].dwOptions);
					dwState |= SETPROP_ERRORS;
					dwState |= SETPROP_WAS_REQUIRED;
					rgDBProp[ulProp].dwStatus = DBPROPSTATUS_BADOPTION;
					_ATLDUMPPROPERTY(rgDBProp[ulProp].dwPropertyID, rgDBProp[ulProp].dwStatus);
					continue;
				}

				// Check that the property is settable
				// We do not check against DBPROPFLAGS_CHANGE here
				if( (pUPropInfo->dwFlags & DBPROPFLAGS_WRITE) == 0 )
				{
					rgDBProp[ulProp].dwStatus = DBPROPSTATUS_OK;

					vDefaultValue.Clear();

					// VT_EMPTY against a read only property should be a no-op since
					// the VT_EMPTY means the default.
					if( V_VT(&rgDBProp[ulProp].vValue) == VT_EMPTY )
					{
						dwState |= SETPROP_VALIDPROP;
						continue;
					}

					if( SUCCEEDED(GetDefaultValue(ulCurSet, rgDBProp[ulProp].dwPropertyID,
							&dwOption, &(vDefaultValue))) )
					{
						if( V_VT(&rgDBProp[ulProp].vValue) ==  V_VT(&vDefaultValue) )
						{
							switch( V_VT(&vDefaultValue) )
							{
								case VT_BOOL:
									if( V_BOOL(&rgDBProp[ulProp].vValue) == V_BOOL(&vDefaultValue) )
									{
										dwState |= SETPROP_VALIDPROP;
										continue;
									}
									break;
								case VT_I2:
									if( V_I2(&rgDBProp[ulProp].vValue) == V_I2(&vDefaultValue) )
									{
										dwState |= SETPROP_VALIDPROP;
										continue;
									}
									break;
								case VT_I4:
									if( V_I4(&rgDBProp[ulProp].vValue) == V_I4(&vDefaultValue) )
									{
										dwState |= SETPROP_VALIDPROP;
										continue;
									}
									break;
								case VT_BSTR:
									if( wcscmp(V_BSTR(&rgDBProp[ulProp].vValue), V_BSTR(&vDefaultValue)) == 0 )
									{
										dwState |= SETPROP_VALIDPROP;
										continue;
									}
									break;
							}
						}
					}

					dwState |= SETPROP_ERRORS;
					dwState |= (rgDBProp[ulProp].dwOptions == DBPROPOPTIONS_REQUIRED) ? SETPROP_WAS_REQUIRED : 0;
					rgDBProp[ulProp].dwStatus = (rgDBProp[ulProp].dwOptions == DBPROPOPTIONS_OPTIONAL) ? DBPROPSTATUS_NOTSET : DBPROPSTATUS_NOTSETTABLE;
					_ATLDUMPPROPERTY(rgDBProp[ulProp].dwPropertyID, rgDBProp[ulProp].dwStatus);
					continue;
				}

				// Check that the property is being set with the correct VARTYPE
				if( (rgDBProp[ulProp].vValue.vt != pUPropInfo->VarType) &&
					(rgDBProp[ulProp].vValue.vt != VT_EMPTY) )
				{
					// The SQL Native Client OLE DB provider uses different authentication based on the content of the VARIANT.
					// If the variant is an empty VT_BSTR, Windows Authentication Mode is used to authorize user access to the SQL Server database
					// If the variant is VT_EMPTY,  SQL Server security is used to authorize user access to the SQL Server database
					if (rgDBProp[ulProp].dwPropertyID != DBPROP_AUTH_INTEGRATED &&
						(rgDBProp[ulProp].vValue.vt != VT_EMPTY && rgDBProp[ulProp].vValue.vt != VT_BSTR))
					{
						dwState |= SETPROP_ERRORS;
						dwState |= (rgDBProp[ulProp].dwOptions == DBPROPOPTIONS_REQUIRED) ? SETPROP_WAS_REQUIRED : 0;
						rgDBProp[ulProp].dwStatus = DBPROPSTATUS_BADVALUE;
						_ATLDUMPPROPERTY(rgDBProp[ulProp].dwPropertyID, rgDBProp[ulProp].dwStatus);
						continue;
					}
				}

				// Check that the value is legal
				if( (rgDBProp[ulProp].vValue.vt != VT_EMPTY) &&
					IsValidValue(ulCurSet, &(rgDBProp[ulProp])) == S_FALSE )
				{
					dwState |= SETPROP_ERRORS;
					dwState |= (rgDBProp[ulProp].dwOptions == DBPROPOPTIONS_REQUIRED) ? SETPROP_WAS_REQUIRED : 0;
					rgDBProp[ulProp].dwStatus = DBPROPSTATUS_BADVALUE;
					_ATLDUMPPROPERTY(rgDBProp[ulProp].dwPropertyID, rgDBProp[ulProp].dwStatus);
					continue;
				}


				// Check for a bad COLID, we only catch bad DBIDs
				if( pUPropInfo->dwFlags & DBPROPFLAGS_COLUMNOK )
				{
					if( CDBIDOps::IsValidDBID(&(rgDBProp[ulProp].colid)) == S_FALSE )
					{
						dwState |= SETPROP_ERRORS;
						dwState |= (rgDBProp[ulProp].dwOptions == DBPROPOPTIONS_REQUIRED) ? SETPROP_WAS_REQUIRED : 0;
						rgDBProp[ulProp].dwStatus = DBPROPSTATUS_BADCOLUMN;
						_ATLDUMPPROPERTY(rgDBProp[ulProp].dwPropertyID, rgDBProp[ulProp].dwStatus);
						continue;
					}
					dwState |= SETPROP_COLUMN_LEVEL;

				}

				if(SetProperty(ulCurSet, ulCurProp, /*pUPropInfo,*/ &(rgDBProp[ulProp])) == S_OK)
				{
					dwState |= SETPROP_VALIDPROP;
				}
				else
				{
					dwState |= SETPROP_ERRORS;
					dwState |= (rgDBProp[ulProp].dwOptions == DBPROPOPTIONS_REQUIRED) ? SETPROP_WAS_REQUIRED : 0;
				}
				_ATLDUMPPROPERTY(rgDBProp[ulProp].dwPropertyID, rgDBProp[ulProp].dwStatus);
			}
		}

		vDefaultValue.Clear();

		// At least one propid was marked as not S_OK
		if( dwState & SETPROP_ERRORS )
		{
			if (!bIsCreating)
			{
				return (dwState & SETPROP_VALIDPROP) ? DB_S_ERRORSOCCURRED : DB_E_ERRORSOCCURRED;
			}
			else
			{
				return (dwState & SETPROP_WAS_REQUIRED) ? DB_E_ERRORSOCCURRED : DB_S_ERRORSOCCURRED;
			}
		}

		return S_OK;
	}
ATLPREFAST_UNSUPPRESS()

	OUT_OF_LINE HRESULT	CopyUPropVal(
		_In_ ULONG iPropSet,
		_Inout_ UPROPVAL* rgUPropVal)
	{
		HRESULT	hr = S_OK;
		UPROP* pUProp;
		UPROPVAL* pUPropVal;
		DBPROP dbProp;

		ATLENSURE_RETURN(rgUPropVal);
		ATLASSERT(iPropSet < m_cUPropSet);

		VariantInit(&dbProp.vValue);

		pUProp = &(m_pUProp[iPropSet]);
		for(ULONG ul=0; ul<pUProp->cPropIds; ul++)
		{
			pUPropVal = &(pUProp->pUPropVal[ul]);

			// Transfer dwOptions
			rgUPropVal[ul].dwOption = pUPropVal->dwOption;

			// Transfer Flags
			rgUPropVal[ul].dwFlags = pUPropVal->dwFlags;

			// Transfer Column Properties
			if( pUPropVal->pCColumnIds )
			{
				rgUPropVal[ul].pCColumnIds = _ATL_NEW CColumnIds;
ATLPREFAST_SUPPRESS(6385)
				if( rgUPropVal[ul].pCColumnIds )
ATLPREFAST_UNSUPPRESS()
				{
					CColumnIds* pColIds = pUPropVal->pCColumnIds;
					for (size_t i = 0; i < pColIds->GetCount(); i++)
					{
						hr = (pUPropVal->pCColumnIds)->GetValue(i, &(dbProp.dwOptions),&(dbProp.colid), &(dbProp.vValue));
						if( FAILED(hr) )
							goto EXIT;
						if( FAILED(hr = (rgUPropVal[ul].pCColumnIds)->AddColumnId(&dbProp)) )
							goto EXIT;
					}
				}
				else
				{
					hr = E_OUTOFMEMORY;
					goto EXIT;
				}
			}
			else
			{
				rgUPropVal[ul].pCColumnIds = NULL;
			}

			// Transfer value
			VariantInit(&(rgUPropVal[ul].vValue));
			if( FAILED(hr = VariantCopy(&(rgUPropVal[ul].vValue),
				&(pUPropVal->vValue))) )
				goto EXIT;
		}

EXIT:
		VariantClear(&(dbProp.vValue));
		return hr;
	}
	void ClearPropertyInError()
	{
		ATLASSUME( m_rgdwPropsInError );
		memset(m_rgdwPropsInError, 0, m_cUPropSet * m_cElemPerSupported * sizeof(DWORD));
	}

	void CopyUPropSetsSupported(_Out_ DWORD* rgdwSupported)
	{
		Checked::memcpy_s(rgdwSupported, m_cUPropSet * m_cElemPerSupported * sizeof(DWORD),
			m_rgdwSupported, m_cUPropSet * m_cElemPerSupported * sizeof(DWORD));
	}

	virtual HRESULT	InitUPropSetsSupported() = 0;

	virtual HRESULT	GetIndexofPropSet(
		_In_ const GUID* pPropSet,
		_Out_ ULONG* pulCurSet) = 0;

	ULONG GetCountofWritablePropsInPropSet(_In_ ULONG iPropSet)
	{
		ULONG cWritable = 0;
		UPROPINFO* pUPropInfo;

		ATLENSURE( m_pUPropSet );
		ATLASSERT( iPropSet < m_cUPropSet );

		pUPropInfo = m_pUPropSet[iPropSet].pUPropInfo;

		for(ULONG ul=0; ul<m_pUPropSet[iPropSet].cUPropInfo; ul++)
		{
			if( pUPropInfo[ul].dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
				cWritable++;
		}

		return cWritable;
	}

	void CopyUPropInfo(
		_In_ ULONG iPropSet,
		_Outptr_ UPROPINFO** rgpUPropInfo)
	{
		ATLASSUME( rgpUPropInfo != NULL );
		ATLASSUME( iPropSet < m_cUPropSet );
		Checked::memcpy_s(rgpUPropInfo, m_pUProp[iPropSet].cPropIds * sizeof(UPROPINFO*),
			m_pUProp[iPropSet].rgpUPropInfo, m_pUProp[iPropSet].cPropIds * sizeof(UPROPINFO*));
	}

	virtual HRESULT	GetDefaultValue(
		_In_ ULONG iPropSet,
		_In_ DBPROPID dwPropId,
		_Out_ DWORD* pdwOption,
		_Out_ VARIANT* pVar) = 0;

	typedef UPROPSET* (*PGetPropSet)(
		_Out_opt_ ULONG* pNumPropSets,
		_Out_opt_ ULONG* pcElemPerSupported,
		_Out_opt_ UPROPSET* pSet,
		_In_ GUID* pguidSet);

	HRESULT	InternalInitUPropSetsSupported(_In_ PGetPropSet pfnGetSet)
	{
		ULONG cPropSet = 0, cElemsPerSupported = 0;
		int cSets = (int)(DWORD_PTR)(*pfnGetSet)(NULL, &cElemsPerSupported, NULL, (GUID*)&GUID_NULL);
		UPROPSET* pPropSet = (UPROPSET*)::ATL::AtlCoTaskMemCAlloc(static_cast<ULONG>(sizeof(UPROPSET)), cSets);
		if (pPropSet == NULL)
			return E_OUTOFMEMORY;
		pPropSet = (*pfnGetSet)(&cPropSet, &cElemsPerSupported, pPropSet, (GUID*)&GUID_NULL);
		memset(m_rgdwSupported, 0xFFFF, cPropSet * cElemsPerSupported * sizeof(DWORD));
		CoTaskMemFree(pPropSet);
		return S_OK;
	}

	HRESULT	InternalGetDefaultValue(
		_In_ PGetPropSet pfnGetSet,
		_In_ ULONG iPropSet,
		_In_ DBPROPID dwPropId,
		_Out_ DWORD* pdwOption,
		_Out_ VARIANT* pVar)
	{
		if (pdwOption == NULL || pVar == NULL)
			return E_INVALIDARG;

		ULONG cUPropSet = 0, cElemPerSupported =0;

		int cSets = (int)(DWORD_PTR)(*pfnGetSet)(NULL, &cElemPerSupported, NULL, (GUID*)&GUID_NULL);
		UPROPSET* pPropSet = (UPROPSET*)::ATL::AtlCoTaskMemCAlloc(static_cast<ULONG>(sizeof(UPROPSET)), cSets);
		if (pPropSet == NULL)
			return E_OUTOFMEMORY;
		pPropSet = (*pfnGetSet)(&cUPropSet, &cElemPerSupported, pPropSet, (GUID*)&GUID_NULL);

		ATLASSERT(iPropSet < cUPropSet);
		for (ULONG iProp = 0; iProp < pPropSet[iPropSet].cUPropInfo; iProp++)
		{
			UPROPINFO& rInfo = pPropSet[iPropSet].pUPropInfo[iProp];
			if (rInfo.dwPropId == dwPropId)
			{
				HRESULT hr = S_OK;
				pVar->vt = rInfo.VarType;
				*pdwOption = rInfo.dwOption;
				switch(rInfo.VarType)
				{
				case VT_BSTR:
					pVar->bstrVal = SysAllocString(rInfo.szVal);
					if (pVar->bstrVal == NULL && rInfo.szVal != NULL)
						hr = E_OUTOFMEMORY;
					break;
				default:
					pVar->lVal = (DWORD)rInfo.dwVal;
					break;
				}
				CoTaskMemFree(pPropSet);
				return hr;
			}
		}
		CoTaskMemFree(pPropSet);
		return E_FAIL;
	}

	_Success_(return == S_OK) HRESULT InternalFInit(
		_In_ PGetPropSet pfnGetSet,
		_In_opt_ CUtlPropsBase* pCopyMe = NULL)
	{
		HRESULT		hr;
		ULONG		ulPropId;
		ULONG		cPropIds;
		ULONG		iPropSet;
		ULONG		iNewDex;
		UPROPINFO*	pUPropInfo;

		// If a pointer is passed in, we should copy that property object
		if( pCopyMe )
		{
			// Establish some base values
			m_cUPropSet = pCopyMe->m_cUPropSet;
			CoTaskMemFree(m_pUPropSet);
			m_pUPropSet = (UPROPSET*)::ATL::AtlCoTaskMemCAlloc(static_cast<ULONG>(sizeof(UPROPSET)), m_cUPropSet);
			if (m_pUPropSet == NULL)
				return E_OUTOFMEMORY;
			Checked::memcpy_s(m_pUPropSet, sizeof(UPROPSET) * m_cUPropSet, pCopyMe->m_pUPropSet, sizeof(UPROPSET) * m_cUPropSet);
			m_cElemPerSupported = pCopyMe->m_cElemPerSupported;
			ATLASSERT( (m_cUPropSet != 0)  && (m_cElemPerSupported != 0) );
			// Retrieve Supported Bitmask
			ATLTRY(m_rgdwSupported.Allocate(::ATL::AtlMultiplyThrow(m_cUPropSet, m_cElemPerSupported)));
			ATLTRY(m_rgdwPropsInError.Allocate(::ATL::AtlMultiplyThrow(m_cUPropSet, m_cElemPerSupported)));
			if( m_rgdwSupported == NULL|| m_rgdwPropsInError == NULL)
			{
				m_rgdwSupported.Free();
				m_rgdwPropsInError.Free();
				return E_OUTOFMEMORY;
			}
			ClearPropertyInError();
			pCopyMe->CopyUPropSetsSupported(m_rgdwSupported);

		}
		else
		{
			int cSets = (int)(DWORD_PTR)(*pfnGetSet)(NULL, &m_cElemPerSupported, NULL, (GUID*)&GUID_NULL);
			UPROPSET* pSet = (UPROPSET*)::ATL::AtlCoTaskMemCAlloc(static_cast<ULONG>(sizeof(UPROPSET)), cSets);
			if (pSet == NULL)
				return E_OUTOFMEMORY;
			pSet = (*pfnGetSet)(&m_cUPropSet, &m_cElemPerSupported, pSet, (GUID*)&GUID_NULL);
			CoTaskMemFree(m_pUPropSet);
			m_pUPropSet = pSet;
			ATLASSERT( (m_cUPropSet != 0)  && (m_cElemPerSupported != 0) );
			if( !m_cUPropSet || !m_cElemPerSupported )
				return E_FAIL;

			ATLTRY(m_rgdwSupported.Allocate(::ATL::AtlMultiplyThrow(m_cUPropSet, m_cElemPerSupported)));
			ATLTRY(m_rgdwPropsInError.Allocate(::ATL::AtlMultiplyThrow(m_cUPropSet, m_cElemPerSupported)));
			if( m_rgdwSupported == NULL || m_rgdwPropsInError == NULL)
			{
				m_rgdwSupported.Free();
				m_rgdwPropsInError.Free();
				return E_OUTOFMEMORY;
			}
			else
				ClearPropertyInError();

			if( FAILED(hr = InitUPropSetsSupported()) )
			{
				m_rgdwSupported.Free();
				return hr;
			}
		}

		// Allocate UPROPS structures for the count of Property sets
		if(m_pUProp.Allocate(m_cUPropSet))
		{
			memset(m_pUProp, 0, m_cUPropSet * sizeof(UPROP));
		}
		else
		{
			m_cUPropSet = 0;
			return E_OUTOFMEMORY;
		}

		// With in the UPROPS Structure allocate and intialize the
		// Property IDs that belong to this property set.
		for(iPropSet=0; iPropSet<m_cUPropSet; iPropSet++)
		{
			cPropIds = GetCountofWritablePropsInPropSet(iPropSet);

			if( cPropIds > 0 )
			{
				CAutoVectorPtr< UPROPINFO* > rgpUPropInfo;
				CAutoVectorPtr< UPROPVAL > rgUPropVal;

				rgpUPropInfo.Allocate( cPropIds );
				rgUPropVal.Allocate( cPropIds );
				if ( (rgpUPropInfo == NULL) || (rgUPropVal == NULL) )
				{
					return E_OUTOFMEMORY;
				}
				if( pCopyMe )
				{
					pCopyMe->CopyUPropInfo(iPropSet, rgpUPropInfo);
					if( FAILED(hr = pCopyMe->CopyUPropVal(iPropSet, rgUPropVal)) )
						return hr;
				}
				else
				{
					// Clear Pointer Array
#pragma warning(suppress : 28313) // The C28313 warning associated with the following line is spurious.
					memset(static_cast<void*>(rgpUPropInfo), 0, cPropIds * sizeof(UPROPINFO*));

					// Set Pointer to correct property ids with a property set
					pUPropInfo = m_pUPropSet[iPropSet].pUPropInfo;

					// Set up the writable property buffers
					iNewDex = 0;
					for(ulPropId=0; ulPropId<m_pUPropSet[iPropSet].cUPropInfo; ulPropId++)
					{
						if( pUPropInfo[ulPropId].dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
						{
							// Following ATLASSERT indicates that the are more
							// writable properties then space allocated
							ATLASSERT(iNewDex < cPropIds);

							rgpUPropInfo[iNewDex] = &(pUPropInfo[ulPropId]);
							rgUPropVal[iNewDex].dwOption = DBPROPOPTIONS_SETIFCHEAP;
							rgUPropVal[iNewDex].pCColumnIds = NULL;
							rgUPropVal[iNewDex].dwFlags = 0;
							VariantInit(&(rgUPropVal[iNewDex].vValue));
							GetDefaultValue(iPropSet, pUPropInfo[ulPropId].dwPropId,
								&(rgUPropVal[iNewDex].dwOption), &(rgUPropVal[iNewDex].vValue));
							iNewDex++;
						}
					}

					ATLASSERT(cPropIds == iNewDex);
				}


				m_pUProp[iPropSet].rgpUPropInfo = rgpUPropInfo.Detach();
				m_pUProp[iPropSet].pUPropVal = rgUPropVal.Detach();
				m_pUProp[iPropSet].cPropIds = cPropIds;
			}
		}

		// Finally determine if there are any hidden property sets..  Those
		// that do not show up in GetPropertyInfo and should not be returns on
		// a 0, NULL call to GetProperties
		for(iPropSet=0; iPropSet<m_cUPropSet; iPropSet++)
		{
			if( m_pUPropSet[iPropSet].dwFlags & UPROPSET_HIDDEN )
				m_cUPropSetHidden++;
		}

		return S_OK;
	}
	//Check the arguments for Set Properties
	static HRESULT SetPropertiesArgChk(
		_In_ const ULONG cPropertySets,
		_In_reads_(cPropertySets) const DBPROPSET rgPropertySets[])
	{
		if( cPropertySets > 0 && !rgPropertySets )
			return E_INVALIDARG;

		// New argument check for > 1 cPropertyIDs and NULL pointer for
		// array of property ids.
		for(ULONG ul=0; ul<cPropertySets; ul++)
		{
			if( rgPropertySets[ul].cProperties && !(rgPropertySets[ul].rgProperties) )
				return E_INVALIDARG;
		}

		return S_OK;
	}
	HRESULT	GetProperties(
		_In_ const ULONG cPropertySets,
		_In_reads_(cPropertySets) const DBPROPIDSET rgPropertySets[],
		_Out_ ULONG* pcProperties,
		_Outptr_result_buffer_maybenull_(*pcProperties) DBPROPSET** prgProperties,
		_In_ const ULONG cSelectProps = 1,
		_In_reads_(cSelectProps) const GUID* const ppGuid[] = NULL)
	{
		UPROPVAL*		pUPropVal;
		ULONG			ulCurProp;
		ULONG			cTmpPropertySets = cPropertySets;
		HRESULT			hr = S_OK;
		ULONG			ulSet = 0;
		ULONG			ulNext = 0;
		ULONG			cSets = 0;
		ULONG			cProps = 0;
		ULONG			ulProp = 0;
		DWORD			dwStatus = 0;
		DBPROP*			pProp = NULL;
		DBPROP*			pCurProp = NULL;
		DBPROPSET*		pPropSet = NULL;
		UPROPINFO*		pUPropInfo = NULL;
		ULONG			ulCurSet = 0;
		ULONG			iPropSet;
		CAutoVectorPtr< ULONG > piIndex;

		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);

		// ppGuid contains an array of GUIDs that the consumer can retrieve.
		// This is based upon the interface calling this function
		ATLENSURE_RETURN(ppGuid != NULL);

		// We need to have special handling for DBPROPSET_PROPERTIESINERROR..
		// Turn on a flags to indicate this mode and make cTmpPropertySets
		// appear to be 0
		if( (m_dwFlags & ARGCHK_PROPERTIESINERROR) &&
			rgPropertySets &&
			(rgPropertySets[0].guidPropertySet == DBPROPSET_PROPERTIESINERROR) )
		{
			cTmpPropertySets = 0;
			dwStatus |= GETPROP_PROPSINERROR;
		}

		// If the consumer does not restrict the property sets
		// by specify an array of property sets and a cTmpPropertySets
		// greater than 0, then we need to make sure we
		// have some to return
		if( cTmpPropertySets == 0 )
		{
			// There are times when we are called from IRowsetInfo, ISessionProperties, etc.
			// where we should return only the appropriate rowset when cTmpPropertySets is
			// zero.  This solves the problem if the user has more than one set specified in
			// their PROPSET_MAP.

			// Determine the number of property sets supported
			if (ppGuid == NULL)
			{
				cSets = m_cUPropSet;
			}
			else
			{
				ULONG ulActualProps = 0;
				CAutoVectorPtr < ULONG > piSetIndex;

				piSetIndex.Allocate( cSelectProps );
				if (piSetIndex == NULL)
					return E_OUTOFMEMORY;

				// Also, find the index for the set we are looking for
				ULONG l;
				for (l=0; l<cSelectProps; l++)
				{
					for (piSetIndex[l]=0; piSetIndex[l]<m_cUPropSet; piSetIndex[l]++)
					{
						if (InlineIsEqualGUID(*m_pUPropSet[piSetIndex[l]].pPropSet, *ppGuid[l]))
						{
							ulActualProps++;
							break;
						}
					}
				}

				cSets = ulActualProps;
				ulActualProps = 0;
				piIndex.Allocate( cSets );
				if (piIndex == NULL)
					return E_OUTOFMEMORY;
				for (l=0; l<cSelectProps; l++)
				{
					if (piSetIndex[l] != m_cUPropSet) // this is an invalid index
						piIndex[ulActualProps++] = piSetIndex[l];
				}

			}
		}
		else
		{
			// Since special property set guids are not supported by
			// GetProperties, we can just use the count of property
			// sets given to us.
			cSets = cTmpPropertySets;
		}

		// If no properties set, then return
		if( cSets == 0 )
			return S_OK;

		// Allocate the DBPROPSET structures
		pPropSet = (DBPROPSET*)::ATL::AtlCoTaskMemCAlloc(cSets, static_cast<ULONG>(sizeof(DBPROPSET)));
		if(pPropSet)
		{
			memset(pPropSet, 0, cSets * sizeof(DBPROPSET));

			// Fill in the output array
			iPropSet = 0;
			for(ulSet=0; ulSet<cSets; ulSet++)
			{
				// Depending of if Property sets are specified store the
				// return property set.
				if( cTmpPropertySets == 0 )
				{
					ULONG lSet;

					if (ppGuid[ulSet] == NULL)
						lSet = ulSet;
					else
						lSet = piIndex[ulSet];
					if( m_pUPropSet[lSet].dwFlags & UPROPSET_HIDDEN )
						continue;

					pPropSet[iPropSet].guidPropertySet = *(m_pUPropSet[lSet].pPropSet);

				}
				else
				{
					pPropSet[iPropSet].guidPropertySet = rgPropertySets[ulSet].guidPropertySet;
				}
				iPropSet++;
			}
		}
		else
		{
			ATLTRACE(atlTraceDBProvider, 0,
				"Could not allocate DBPROPSET array for GetProperties\n");
			return E_OUTOFMEMORY;
		}

		// Process requested or derived Property sets
		iPropSet=0;
		for(ulSet=0; ulSet<cSets; ulSet++)
		{
			cProps	= 0;
			pProp	= NULL;
			ulNext	= 0;
			dwStatus &= (GETPROP_ERRORSOCCURRED | GETPROP_VALIDPROP | GETPROP_PROPSINERROR);

			// Calculate the number of property nodes needed for this
			// property set.
			if( cTmpPropertySets == 0 )
			{
				ULONG lSet;

				if (ppGuid[ulSet] == NULL)
					lSet = ulSet;
				else
					lSet = piIndex[ulSet];

				// If processing requesting all property sets, do not
				// return the hidden sets.
				if( m_pUPropSet[lSet].dwFlags & UPROPSET_HIDDEN )
					continue;

				cProps = m_pUPropSet[lSet].cUPropInfo;

				// Add Enough space for node that are colid specific
				cProps += GetCountofColids(&(m_pUProp[lSet]));
				dwStatus |= GETPROP_ALLPROPIDS;
				ulCurSet = lSet;
			}
			else
			{
				ATLASSERT(ulSet == iPropSet);

				// If the count of PROPIDs is 0 or It is a special property set, then
				// the consumer is requesting all propids for this property set.
				if(rgPropertySets[ulSet].cPropertyIDs == 0)
				{
					dwStatus |= GETPROP_ALLPROPIDS;
					// We have to determine if the property set is supported and if so
					// the count of properties in the set.
					BOOL bAvailable = false;
					for (ULONG l=0; l<cSelectProps; l++)
					{
						if (InlineIsEqualGUID(*ppGuid[l], rgPropertySets[ulSet].guidPropertySet))
							bAvailable |= true;
					}

					if (bAvailable &&
							GetIndexofPropSet(&(pPropSet[iPropSet].guidPropertySet), &ulCurSet) == S_OK)
					{
						cProps += m_pUPropSet[ulCurSet].cUPropInfo;
						// Add Enough space for node that are colid specific
						cProps += GetCountofColids(&m_pUProp[ulCurSet]);
						_ATLDUMPPROPSETIID(pPropSet[iPropSet].guidPropertySet, dwStatus);
					}
					else
					{
						// Not Supported
						dwStatus |= GETPROP_ERRORSOCCURRED;
						_ATLDUMPPROPSETIID(pPropSet[iPropSet].guidPropertySet, dwStatus);
						goto NEXT_SET;
					}
				}
				else
				{
					cProps = rgPropertySets[ulSet].cPropertyIDs;
					// Check to see if this is a supported interface based on ppGuid.
					BOOL bAvailable = false;
					for (ULONG l=0; l<cSelectProps; l++)
					{
						if (InlineIsEqualGUID(*ppGuid[l], rgPropertySets[ulSet].guidPropertySet))
							bAvailable |= true;
					}

					if (!bAvailable ||
						(GetIndexofPropSet(&(pPropSet[iPropSet].guidPropertySet), &ulCurSet) != S_OK))
					{
						dwStatus |= GETPROP_NOTSUPPORTED;
						dwStatus |= GETPROP_ERRORSOCCURRED;
					}

					_ATLDUMPPROPSETIID(pPropSet[iPropSet].guidPropertySet, dwStatus);

				}
			}

			// Allocate DBPROP array
			if( cProps == 0 )			//Possible with Hidden Passthrough sets
				goto NEXT_SET;

			pProp = (DBPROP*)::ATL::AtlCoTaskMemCAlloc(cProps, static_cast<ULONG>(sizeof(DBPROP)));
			if( pProp )
			{
				// Initialize Buffer
				memset(pProp, 0, cProps * sizeof(DBPROP));
				for(ulProp=0; ulProp<cProps; ulProp++)
				{
ATLPREFAST_SUPPRESS(6385)
					VariantInit(&(pProp[ulProp].vValue));
ATLPREFAST_UNSUPPRESS()
					if( dwStatus & GETPROP_NOTSUPPORTED )
					{
						// Not supported, thus we need to mark all as NOT_SUPPORTED
						pProp[ulProp].dwPropertyID	= rgPropertySets[ulSet].rgPropertyIDs[ulProp];
						pProp[ulProp].dwStatus		= DBPROPSTATUS_NOTSUPPORTED;
						_ATLDUMPPROPERTY(pProp[ulProp].dwPropertyID, pProp[ulProp].dwStatus);
					}
				}
				// Make sure we support the property set
				if( dwStatus & GETPROP_NOTSUPPORTED )
				{
					ulNext = cProps;
					goto NEXT_SET;
				}

				// Now that we have determined we can support the property set, we
				// need to gather current property values
				for(ulProp=0; ulProp<cProps; ulProp++)
				{
					pCurProp = &(pProp[ulNext]);

					//Initialize Variant Value
					pCurProp->dwStatus = DBPROPSTATUS_OK;

					// Retrieve current value of properties
					if( dwStatus & GETPROP_ALLPROPIDS )
					{
						// Verify property is supported, if not do not return
						if(!TESTBIT(&(m_rgdwSupported[ulCurSet * m_cElemPerSupported]), ulProp) )
							continue;

						// If we are looking for properties in error, then we should ignore all
						// that are not in error.
						if( (dwStatus & GETPROP_PROPSINERROR) &&
							!TESTBIT(&(m_rgdwPropsInError[ulCurSet * m_cElemPerSupported]), ulProp) )
							continue;

						pUPropInfo = &(m_pUPropSet[ulCurSet].pUPropInfo[ulProp]);

						ATLENSURE_RETURN( pUPropInfo );

						pCurProp->dwPropertyID = pUPropInfo->dwPropId;
						pCurProp->colid = DB_NULLID;

						// If the property is WRITEABLE or CHANGABLE, then the value will
						// be gotten from the UPROPVAL array, else it will be
						// derive from the GetDefaultValue
						if( pUPropInfo->dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
						{
							pUPropVal = &(m_pUProp[ulCurSet].
								pUPropVal[GetUPropValIndex(ulCurSet, pCurProp->dwPropertyID)]);
							ATLENSURE_RETURN( pUPropVal );

							// Check to see if this property supports column level,
							// if so, dump those nodes
							if( pUPropInfo->dwFlags & DBPROPFLAGS_COLUMNOK )
							{
								if( pUPropVal->pCColumnIds )
								{
									RetrieveColumnIdProps(pProp, pUPropVal, &ulNext);
									continue;
								}
							}

							pCurProp->dwOptions = pUPropVal->dwOption;
							hr = VariantCopy(&(pCurProp->vValue), &(pUPropVal->vValue));
						}
						else
						{
							GetDefaultValue(ulCurSet, pUPropInfo->dwPropId,
								&(pCurProp->dwOptions), &(pCurProp->vValue));
						}

						// Return all Properties in Error with CONFLICT status
						if( dwStatus & GETPROP_PROPSINERROR )
							pCurProp->dwStatus = DBPROPSTATUS_CONFLICTING;

						dwStatus |= GETPROP_VALIDPROP;
					}
					else
					{
						// Process Properties based on Restriction array.

						pCurProp->dwPropertyID = rgPropertySets[ulSet].rgPropertyIDs[ulProp];
						pCurProp->colid = DB_NULLID;

						if( GetIndexofPropIdinPropSet(ulCurSet, pCurProp->dwPropertyID,
							&ulCurProp) == S_OK)
						{
							// Supported
							pUPropInfo = &(m_pUPropSet[ulCurSet].pUPropInfo[ulCurProp]);
							ATLENSURE_RETURN( pUPropInfo );

							// If the property is WRITEABLE, then the value will
							// be gotten from the UPROPVAL array, else it will be
							// derive from the GetDefaultValue
							if( pUPropInfo->dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
							{
								pUPropVal = &(m_pUProp[ulCurSet].
									pUPropVal[GetUPropValIndex(ulCurSet, pCurProp->dwPropertyID)]);
								ATLENSURE_RETURN( pUPropVal );

								// Check to see if this property supports column level,
								// if so, dump those nodes
								if( pUPropInfo->dwFlags & DBPROPFLAGS_COLUMNOK )
								{
									if( pUPropVal->pCColumnIds )
									{
										RetrieveColumnIdProps(pProp, pUPropVal, &ulNext);
										continue;
									}
								}
								pCurProp->dwOptions = pUPropVal->dwOption;
								hr = VariantCopy(&(pCurProp->vValue), &(pUPropVal->vValue));
							}
							else
							{
								GetDefaultValue(ulCurSet, pUPropInfo->dwPropId,
									&(pCurProp->dwOptions), &(pCurProp->vValue));

							}

							dwStatus |= GETPROP_VALIDPROP;
						}
						else
						{
							// Not Supported
							pCurProp->dwStatus = DBPROPSTATUS_NOTSUPPORTED;
							dwStatus |= GETPROP_ERRORSOCCURRED;
						}
					}

					_ATLDUMPPROPERTY(pCurProp->dwPropertyID, pCurProp->dwStatus);
					// Increment return nodes count
					ulNext++;
				}
			}
			else
			{
				ATLTRACE(atlTraceDBProvider, 0, _T("Could not allocate DBPROP array for GetProperties\n"));
				if( pPropSet )
				{
					//Since we have no properties to return, then we
					//need to free allocated memory and return 0,NULL
					for(ulSet=0; ulSet<cSets; ulSet++)
					{
						// Need to loop through all the VARIANTS and clear them
						for(ulProp=0; ulProp<pPropSet[ulSet].cProperties; ulProp++)
							VariantClear(&(pPropSet[ulSet].rgProperties[ulProp].vValue));
						CoTaskMemFree(pPropSet[ulSet].rgProperties);
					}

					// Free DBPROPSET
					CoTaskMemFree(pPropSet);
				}
				*pcProperties = 0;
				*prgProperties = NULL;
				return E_OUTOFMEMORY;
			}

NEXT_SET:
			// It is possible that all properties are not supported,
			// thus we should delete that memory and set rgProperties
			// to NULL
			if( ulNext == 0 && pProp )
			{
				CoTaskMemFree(pProp);
				pProp = NULL;
			}

			pPropSet[iPropSet].cProperties = ulNext;
			pPropSet[iPropSet].rgProperties = pProp;
			iPropSet++;
		}

		*pcProperties = iPropSet;
		*prgProperties = pPropSet;

		piIndex.Free();

		// At least one propid was marked as not S_OK
		if( dwStatus & GETPROP_ERRORSOCCURRED )
		{
			// If at least 1 property was set
			if( dwStatus & GETPROP_VALIDPROP )
				return DB_S_ERRORSOCCURRED;
			else
			{
				// Do not free any of the memory on a DB_E_
				return DB_E_ERRORSOCCURRED;
			}
		}

		return S_OK;
	}

	ULONG GetCountofColids(_In_ UPROP* pUProp)
	{
		ULONG	cExtra=0;
		ATLENSURE(pUProp);
		for(ULONG ul=0; ul<pUProp->cPropIds; ul++)
		{
			if( pUProp->pUPropVal[ul].pCColumnIds )
				cExtra += (pUProp->pUPropVal[ul].pCColumnIds)->GetCountOfPropColids();
		}
		return cExtra;
	}

	ULONG GetUPropValIndex(
		_In_ ULONG iCurSet,
		_In_ DBPROPID dwPropId)
	{
		for(ULONG ul=0; ul<m_pUProp[iCurSet].cPropIds; ul++)
		{
			if( (m_pUProp[iCurSet].rgpUPropInfo[ul])->dwPropId == dwPropId )
				return ul;
		}
		return 0;
	}

	void RetrieveColumnIdProps(
		_In_ DBPROP* pCurProp,
		_In_ UPROPVAL* pUPropVal,
		_Inout_ ULONG* pulNext)
	{
		// Reset to first Node
		CColumnIds* pColIds = pUPropVal->pCColumnIds;
		HRESULT hr = E_FAIL;
		for (size_t i = 0; i < pColIds->GetCount(); i++)
		{
			CPropColID colId;
			hr = pColIds->GetValue(i, &(pCurProp->dwOptions), &(pCurProp->colid),&(pCurProp->vValue));
			if (SUCCEEDED(hr))
				pCurProp = &(pCurProp[++(*pulNext)]);
		}
		(*pulNext)++;
	}

	//Check the arguments for Retrieve Properties
	HRESULT	GetPropertiesArgChk(
		_In_ const ULONG cPropertySets,
		_In_reads_(cPropertySets) const DBPROPIDSET rgPropertySets[],
		_Out_ ULONG* pcProperties,
		_Outptr_result_buffer_maybenull_(*pcProperties) DBPROPSET** prgProperties)
	{
		// Initialize values
		if(pcProperties)
			*pcProperties = 0;
		if(prgProperties)
			*prgProperties = NULL;

		// Check Arguments
		if( ((cPropertySets > 0) && !rgPropertySets) || !pcProperties || !prgProperties )
			return E_INVALIDARG;

		// New argument check for > 1 cPropertyIDs and NULL pointer for
		// array of property ids.
		for(ULONG ul=0; ul<cPropertySets; ul++)
		{
			if( rgPropertySets[ul].cPropertyIDs && !(rgPropertySets[ul].rgPropertyIDs) )
				return E_INVALIDARG;

			// Check for propper formation of DBPROPSET_PROPERTIESINERROR
			if( (m_dwFlags & ARGCHK_PROPERTIESINERROR) &&
				rgPropertySets[ul].guidPropertySet == DBPROPSET_PROPERTIESINERROR )
			{
				if( (cPropertySets > 1) ||
					(rgPropertySets[ul].cPropertyIDs != 0) ||
					(rgPropertySets[ul].rgPropertyIDs != NULL) )
					return E_INVALIDARG;
			}
		}

		return S_OK;
	}

	OUT_OF_LINE HRESULT	FInit(_In_opt_ CUtlPropsBase* pCopyMe = NULL) = 0;
};

// Implementation Class

/* Function detection for CreateAccessor. Signature is:

STDMETHOD(CreateAccessor)(
_In_ DBACCESSORFLAGS dwAccessorFlags,
_In_ DBCOUNTITEM cBindings,
_In_reads_(cBindings) const DBBINDING rgBindings[],
_In_ DBLENGTH cbRowSize,
_Out_ HACCESSOR *phAccessor,
_Out_writes_(cBindings) DBBINDSTATUS rgStatus[])
*/
template<class T, class = void>
struct Has_CreateAccessor : ::std::false_type {};

template<class T>
struct Has_CreateAccessor<T, ::std::void_t<
	decltype(::std::declval<T>().CreateAccessor(
		DBACCESSORFLAGS{},
		DBCOUNTITEM{},
		::std::declval<const DBBINDING*>(),
		DBLENGTH{},
		::std::declval<HACCESSOR*>(),
		::std::declval<DBBINDSTATUS*>())) >> : ::std::true_type {};

template <class T>
class ATL_NO_VTABLE CUtlProps :
	public CUtlPropsBase
{
public:

	CUtlProps(_In_ DWORD dwFlags = 0)
	{
		ClearMemberVars();
		m_dwFlags = dwFlags;
	}
	virtual ~CUtlProps()
	{
		FreeMemory();
	}
	void FreeMemory()
	{
		// Remove Property Information
		if( m_pUProp )
		{
			for(ULONG ulPropSet=0; ulPropSet<m_cUPropSet; ulPropSet++)
			{
				UPROPVAL* pUPropVal = m_pUProp[ulPropSet].pUPropVal;
				for(ULONG ulPropId=0; ulPropId<m_pUProp[ulPropSet].cPropIds; ulPropId++)
				{
					delete pUPropVal[ulPropId].pCColumnIds;
					VariantClear(&(pUPropVal[ulPropId].vValue));
				}
				delete[] m_pUProp[ulPropSet].rgpUPropInfo;
				delete[] m_pUProp[ulPropSet].pUPropVal;
			}

		}

		m_pUProp.Free();
		m_rgdwSupported.Free();
		m_rgdwPropsInError.Free();
		m_rgiPropSetDex.Free();
		CoTaskMemFree(m_pUPropSet);
		ClearMemberVars();
	}
	void ClearMemberVars()
	{
		m_cPropSetDex		= 0;
		m_cUPropSet			= 0;
		m_cUPropSetHidden	= 0;
		m_pUPropSet			= NULL;

		m_dwFlags			= 0;

		m_cElemPerSupported	= 0;
	}

	//Retrieve the property set indexes that match this property set.
	HRESULT	GetPropertySetIndex(_In_ GUID* pPropertySet)
	{
		DWORD	dwFlag = 0;
		ULONG	ulSet;

		ATLASSUME( m_cUPropSet && m_pUPropSet );
		ATLASSUME( m_rgiPropSetDex );
		ATLASSERT( pPropertySet );

		m_cPropSetDex = 0;

		if( *pPropertySet == DBPROPSET_DATASOURCEALL )
		{
			dwFlag = DBPROPFLAGS_DATASOURCE;
		}
		else if( *pPropertySet == DBPROPSET_DATASOURCEINFOALL )
		{
			dwFlag = DBPROPFLAGS_DATASOURCEINFO;
		}
		else if( *pPropertySet == DBPROPSET_ROWSETALL )
		{
			dwFlag = DBPROPFLAGS_ROWSET;
		}
		else if( *pPropertySet == DBPROPSET_DBINITALL )
		{
			dwFlag = DBPROPFLAGS_DBINIT;
		}
		else if( *pPropertySet == DBPROPSET_SESSIONALL )
		{
			dwFlag = DBPROPFLAGS_SESSION;
		}
		else if( *pPropertySet == DBPROPSET_COLUMNALL)
		{
			dwFlag = DBPROPFLAGS_COLUMN;
		}
		else if( *pPropertySet == DBPROPSET_INDEXALL)
		{
			dwFlag = DBPROPFLAGS_INDEX;
		}
		else if( *pPropertySet == DBPROPSET_TABLEALL)
		{
			dwFlag = DBPROPFLAGS_TABLE;
		}
		else if( *pPropertySet == DBPROPSET_TRUSTEEALL)
		{
			dwFlag = DBPROPFLAGS_TRUSTEE;
		}
		else if( *pPropertySet == DBPROPSET_VIEWALL)
		{
			dwFlag = DBPROPFLAGS_VIEW;
		}
		else // No scan required, just look for match.
		{
			for(ulSet=0; ulSet<m_cUPropSet; ulSet++)
			{
				if( *(m_pUPropSet[ulSet].pPropSet) == *pPropertySet )
				{
					m_rgiPropSetDex[m_cPropSetDex] = ulSet;
					m_cPropSetDex++;
					break;
				}
			}
			goto EXIT;
		}

		// Scan through the property sets looking for matching attributes
		for(ulSet=0; ulSet<m_cUPropSet; ulSet++)
		{
			if( m_pUPropSet[ulSet].pUPropInfo[0].dwFlags & dwFlag )
			{
				m_rgiPropSetDex[m_cPropSetDex] = ulSet;
				m_cPropSetDex++;
			}
		}

EXIT:
		return (m_cPropSetDex) ? S_OK : S_FALSE;
	}

	OUT_OF_LINE HRESULT	GetDefaultValue(
		_In_ ULONG iPropSet,
		_In_ DBPROPID dwPropId,
		_Out_ DWORD* pdwOption,
		_Out_ VARIANT* pVar)
	{
		return InternalGetDefaultValue(T::_GetPropSet, iPropSet, dwPropId, pdwOption, pVar);
	}

	OUT_OF_LINE HRESULT	FInit(_In_opt_ CUtlPropsBase* pCopyMe = NULL)
	{
		return InternalFInit(T::_GetPropSet, pCopyMe);
	}
	HRESULT	FillDefaultValues(_In_ ULONG ulPropSetTarget = ULONG_MAX)
	{
		HRESULT		hr;
		ULONG		ulPropId;
		ULONG		iPropSet;
		ULONG		iNewDex;

		// Fill in all the actual values.
		// Typically because we now have an hdbc with which to get them.
		// (Or we no longer have an hdbc, so must clear them.)
		// Note that the UPROP (with values) array may be a subset of the UPROPINFO array.
		// Only writable properties are in UPROP array.

		// Maybe restrict to a single PropSet if within valid range [0...m_cUPropSet-1].
		// Otherwise do all propsets.
		iPropSet = (ulPropSetTarget < m_cUPropSet) ? ulPropSetTarget : 0;

		for( ; iPropSet<m_cUPropSet; iPropSet++)
		{
			iNewDex = 0;
			for(ulPropId=0; ulPropId<m_pUPropSet[iPropSet].cUPropInfo; ulPropId++)
			{
				if( m_pUPropSet[iPropSet].pUPropInfo[ulPropId].dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
				{
					//Initialize dwFlags element of UPropVal
					m_pUProp[iPropSet].pUPropVal[iNewDex].dwFlags = 0;

					// Don't need this since SetProperties() resets these.
					//ATLASSUME( m_pUProp[iPropSet].pUPropVal[iNewDex].dwOption == DBPROPOPTIONS_SETIFCHEAP);
					ATLASSUME( m_pUProp[iPropSet].pUPropVal[iNewDex].pCColumnIds == NULL);

					VariantClear(&m_pUProp[iPropSet].pUPropVal[iNewDex].vValue);
					hr = GetDefaultValue(
							iPropSet,
							m_pUPropSet[iPropSet].pUPropInfo[ulPropId].dwPropId,
							&m_pUProp[iPropSet].pUPropVal[iNewDex].dwOption,
							&m_pUProp[iPropSet].pUPropVal[iNewDex].vValue );
					if (FAILED(hr))
						return hr;
					iNewDex++;
				}
			}

			// We're through if restricting to single PropSet.
			if (ulPropSetTarget < m_cUPropSet)
				break;
		}
		return NOERROR;
	}

	// Translate Rowset IIDs to PROPSET structures ready to pass to SetProperties
	HRESULT	ConvertRowsetIIDtoDBPROPSET(
		_In_ const IID* piid,
		_Inout_ DBPROPSET* pPropSet)
	{
		ATLASSUME( piid != 0 && pPropSet != 0 );
		ATLASSUME( (pPropSet->cProperties == 1) || (pPropSet->rgProperties) );

		HRESULT	hr = S_OK;
		DBPROP* pProp = &(pPropSet->rgProperties[0]);

		if(InlineIsEqualGUID(*piid, __uuidof(IAccessor)))
			pProp->dwPropertyID = DBPROP_IAccessor;
		else if(InlineIsEqualGUID(*piid,__uuidof(IColumnsInfo)))
			pProp->dwPropertyID = DBPROP_IColumnsInfo;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowset)))
			pProp->dwPropertyID = DBPROP_IRowset;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetInfo)))
			pProp->dwPropertyID = DBPROP_IRowsetInfo;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetLocate)))
			pProp->dwPropertyID = DBPROP_IRowsetLocate;
		else if(InlineIsEqualGUID(*piid , __uuidof(IColumnsRowset)))
			pProp->dwPropertyID = DBPROP_IColumnsRowset;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetResynch)))
			pProp->dwPropertyID = DBPROP_IRowsetResynch;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetScroll)))
			pProp->dwPropertyID = DBPROP_IRowsetScroll;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetChange)))
			pProp->dwPropertyID = DBPROP_IRowsetChange;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetUpdate)))
			pProp->dwPropertyID = DBPROP_IRowsetUpdate;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetIdentity)))
			pProp->dwPropertyID = DBPROP_IRowsetIdentity;
		else if(InlineIsEqualGUID(*piid , __uuidof(IConnectionPointContainer)))
			pProp->dwPropertyID = DBPROP_IConnectionPointContainer;
		else if(InlineIsEqualGUID(*piid , __uuidof(ISupportErrorInfo)))
			pProp->dwPropertyID = DBPROP_ISupportErrorInfo;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetIndex)))
			pProp->dwPropertyID = DBPROP_IRowsetIndex;
	#if( OLEDBVER >= 0x0200 && 0 /* These interfaces are defined nowhere */ )
		else if(InlineIsEqualGUID(*piid , __uuidof(IProvideMoniker)))
			pProp->dwPropertyID = DBPROP_IProvideMoniker;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetNotify)))
			pProp->dwPropertyID = DBPROP_IRowsetNotify;
		else if(InlineIsEqualGUID(*piid , __uuidof(IReadData)))
			pProp->dwPropertyID = DBPROP_IReadData;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetExactScroll)))
			pProp->dwPropertyID = DBPROP_IRowsetExactScroll;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetNextRowset)))
			pProp->dwPropertyID = DBPROP_IRowsetNextRowset;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetNewRowAfter)))
			pProp->dwPropertyID = DBPROP_IRowsetNewRowAfter;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetWithParameters)))
			pProp->dwPropertyID = DBPROP_IRowsetWithParameters;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetFind)))
			pProp->dwPropertyID = DBPROP_IRowsetFind;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetAsynch)))
			pProp->dwPropertyID = DBPROP_IRowsetAsynch;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetKeys)))
			pProp->dwPropertyID = DBPROP_IRowsetKeys;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetWatchAll)))
			pProp->dwPropertyID = DBPROP_IRowsetWatchAll;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetWatchNotify)))
			pProp->dwPropertyID = DBPROP_IRowsetWatchNotify;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetWatchRegion)))
			pProp->dwPropertyID = DBPROP_IRowsetWatchRegion;
		else if(InlineIsEqualGUID(*piid , __uuidof(IRowsetCopyRows)))
			pProp->dwPropertyID = DBPROP_IRowsetCopyRows;
	#endif //#if( OLEDBVER >= 0x0200 )
		else
			hr = S_FALSE;

		// If the IID can be mapped to a DBPROPID, the
		// we need to initialize the vValue to TRUE
		if(hr == S_OK)
		{
			// Set PropertySet
			pPropSet->guidPropertySet = DBPROPSET_ROWSET;

			// Set Property
			pProp->dwOptions = DBPROPOPTIONS_REQUIRED;
			pProp->dwStatus = 0;
			pProp->colid = DB_NULLID;

			VariantInit(&(pProp->vValue));
			pProp->vValue.vt = VT_BOOL;
			V_BOOL(&(pProp->vValue)) = ATL_VARIANT_TRUE;
		}

		return hr;
	}

	void SetPropertyInError(
		_In_ const ULONG iPropSet,
		_In_ const ULONG iPropId)
	{
		SETBIT(&(m_rgdwPropsInError[iPropSet * m_cElemPerSupported]), iPropId);
	}

	BOOL IsPropSet(
		_In_ const GUID* pguidPropSet,
		_In_ DBPROPID dwPropId)
	{
		HRESULT		hr;
		ULONG		iPropSet;
		ULONG		iPropId;
		VARIANT		vValue;
		DWORD		dwOptions;

		VariantInit(&vValue);

		if( GetIndexofPropSet(pguidPropSet, &iPropSet) == S_OK )
		{
			if( GetIndexofPropIdinPropSet(iPropSet, dwPropId, &iPropId) == S_OK )
			{
				if( m_pUPropSet[iPropSet].pUPropInfo[iPropId].dwFlags &
					(DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
				{
					ULONG iPropVal = GetUPropValIndex(iPropSet, dwPropId);

					dwOptions = m_pUProp[iPropSet].pUPropVal[iPropVal].dwOption;
					hr = VariantCopy(&vValue, &(m_pUProp[iPropSet].
						pUPropVal[iPropVal].vValue));
				}
				else
				{
					hr = GetDefaultValue(iPropSet, dwPropId,
						&dwOptions, &vValue);
				}

				if( dwOptions == DBPROPOPTIONS_REQUIRED )
				{
					ATLASSERT( vValue.vt == VT_BOOL );
					if( SUCCEEDED(hr) &&
						(V_BOOL(&vValue) != ATL_VARIANT_FALSE) )
					{
						VariantClear(&vValue);
						return TRUE;
					}
				}
			}
		}

		VariantClear(&vValue);
		return FALSE;
	}
	OUT_OF_LINE HRESULT	GetPropValue(
		_In_ const GUID* pguidPropSet,
		_In_ DBPROPID dwPropId,
		_Inout_ VARIANT* pvValue)
	{
		HRESULT		hr = E_FAIL;
		ULONG		iPropSet;
		ULONG		iPropId;
		DWORD		dwOptions;
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);

		if( GetIndexofPropSet(pguidPropSet, &iPropSet) == S_OK )
		{
			if( GetIndexofPropIdinPropSet(iPropSet, dwPropId, &iPropId) == S_OK )
			{
				if( m_pUPropSet[iPropSet].pUPropInfo[iPropId].dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) )
				{
					hr = VariantCopy(pvValue, &(m_pUProp[iPropSet].pUPropVal[
						GetUPropValIndex(iPropSet, dwPropId)].vValue));
				}
				else
				{
					VariantClear(pvValue);

					hr = GetDefaultValue(iPropSet, dwPropId,
						&dwOptions, pvValue);
				}
			}
		}

		return hr;
	}
	HRESULT	SetPropValue(
		_In_ const GUID* pguidPropSet,
		_In_ DBPROPID dwPropId,
		_In_ VARIANT* pvValue)
	{
		HRESULT		hr = E_FAIL;
		ULONG		iPropSet;
		ULONG		iPropId;
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);

		if( GetIndexofPropSet(pguidPropSet, &iPropSet) == S_OK )
		{
			if( GetIndexofPropIdinPropSet(iPropSet, dwPropId, &iPropId) == S_OK )
			{
				ATLASSUME( m_pUPropSet[iPropSet].pUPropInfo[iPropId].dwFlags & (DBPROPFLAGS_WRITE | DBPROPFLAGS_CHANGE) );

				hr = VariantCopy(&(m_pUProp[iPropSet].pUPropVal[
						GetUPropValIndex(iPropSet, dwPropId)].vValue), pvValue);
			}
		}

		return hr;
	}

	//Pointer to properties in error mask
	DWORD* GetPropsInErrorPtr()
	{
		return m_rgdwPropsInError;
	}
	ULONG GetUPropSetCount()
	{
		return m_cUPropSet;
	}
	void SetUPropSetCount(_In_ ULONG c)
	{
		m_cUPropSet = c;
	}

	// NOTE: The following functions depend on all prior
	// properties in the array being writable.
	// This is because the UPROP array contains only writable elements,
	// and the UPROPINFO array contains writable and read-only elements.
	// (If this is a problem, we would need to define which one it came from
	// and add the appropriate ATLASSERTs...)

	//Get DBPROPOPTIONS_xx
	DWORD GetPropOption(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		return m_pUProp[iPropSet].pUPropVal[iProp].dwOption;
	}
	//Set DBPROPOPTIONS_xx
	void SetPropOption(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp,
		_In_ DWORD dwOption)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		m_pUProp[iPropSet].pUPropVal[iProp].dwOption = dwOption;
	}
	//Determine if property is required and variant_true
	BOOL IsRequiredTrue(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		ATLASSUME(m_pUProp[iPropSet].pUPropVal[iProp].vValue.vt == VT_BOOL);

		return( (m_pUProp[iPropSet].pUPropVal[iProp].dwOption == DBPROPOPTIONS_REQUIRED) &&
				(V_BOOL(&m_pUProp[iPropSet].pUPropVal[iProp].vValue) != ATL_VARIANT_FALSE) );
	}
	DWORD GetInternalFlags(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		return m_pUProp[iPropSet].pUPropVal[iProp].dwFlags;
	}
	void AddInternalFlags(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp,
		_In_ DWORD dwFlags)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		m_pUProp[iPropSet].pUPropVal[iProp].dwFlags |= dwFlags;
	}
	void RemoveInternalFlags(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp,
		_In_ DWORD dwFlags)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		m_pUProp[iPropSet].pUPropVal[iProp].dwFlags &= ~dwFlags;
	}
	VARIANT * GetVariant(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		return & m_pUProp[iPropSet].pUPropVal[iProp].vValue;
	}
	HRESULT SetVariant(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp,
		_Out_ VARIANT *pv)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		// Does VariantClear first.
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		return VariantCopy( &m_pUProp[iPropSet].pUPropVal[iProp].vValue, pv );
	}
	void SetValEmpty(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		VariantClear( &m_pUProp[iPropSet].pUPropVal[iProp].vValue );
	}
	BOOL IsEmpty(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		return ( m_pUProp[iPropSet].pUPropVal[iProp].vValue.vt == VT_EMPTY);
	}
	void SetValBool(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp,
		_In_ VARIANT_BOOL bVal)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		// Note that we accept any "true" value.
		VariantClear(&m_pUProp[iPropSet].pUPropVal[iProp].vValue);
		m_pUProp[iPropSet].pUPropVal[iProp].vValue.vt = VT_BOOL;
		V_BOOL(&m_pUProp[iPropSet].pUPropVal[iProp].vValue) = (bVal ? ATL_VARIANT_TRUE : ATL_VARIANT_FALSE);
	}
	VARIANT_BOOL GetValBool(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		ATLASSUME(m_pUProp[iPropSet].pUPropVal[iProp].vValue.vt == VT_BOOL);
		return V_BOOL(&m_pUProp[iPropSet].pUPropVal[iProp].vValue);
	}
	void SetValShort(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp,
		_In_ SHORT iVal)
	{
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		VariantClear(&m_pUProp[iPropSet].pUPropVal[iProp].vValue);
		m_pUProp[iPropSet].pUPropVal[iProp].vValue.vt = VT_I2;
		m_pUProp[iPropSet].pUPropVal[iProp].vValue.iVal = iVal;
	}
	SHORT GetValShort(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		ATLASSUME(m_pUProp[iPropSet].pUPropVal[iProp].vValue.vt == VT_I2);
		return m_pUProp[iPropSet].pUPropVal[iProp].vValue.iVal;
	}
	void SetValLong(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp,
		_In_ LONG lVal)
	{
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		VariantClear(&m_pUProp[iPropSet].pUPropVal[iProp].vValue);
		m_pUProp[iPropSet].pUPropVal[iProp].vValue.vt = VT_I4;
		m_pUProp[iPropSet].pUPropVal[iProp].vValue.lVal = lVal;
	}
	LONG GetValLong(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		ATLASSUME(m_pUProp[iPropSet].pUPropVal[iProp].vValue.vt == VT_I4);
		return m_pUProp[iPropSet].pUPropVal[iProp].vValue.lVal;
	}
	HRESULT SetValString(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp,
		_In_z_ const WCHAR *pwsz)
	{
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		VARIANT *pv = &m_pUProp[iPropSet].pUPropVal[iProp].vValue;
		VariantClear(pv);
		pv->bstrVal = SysAllocString(pwsz);
		if (pv->bstrVal)
			pv->vt = VT_BSTR;
		else
			return E_FAIL;

		// See if this was used for non-string type.
		// Typically this is an easy way to pass integer as a string.
		if (GetExpectedVarType(iPropSet,iProp) == VT_BSTR)
			return NOERROR;
		if (pwsz[0] != L'\0')
		{
			HRESULT hr=VariantChangeType( pv, pv, 0, GetExpectedVarType(iPropSet,iProp) );
			if(FAILED(hr))
			{
				pv->vt = VT_EMPTY;
			}
			return hr;
		}

		// Set to "", which for non-string means empty.
		SysFreeString(pv->bstrVal);
		return NOERROR;
	}
	const WCHAR * GetValString(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		CComCritSecLock<CComAutoCriticalSection> lock(m_oCriticalSection);
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		ATLASSUME(m_pUProp[iPropSet].pUPropVal[iProp].vValue.vt == VT_BSTR);
		return m_pUProp[iPropSet].pUPropVal[iProp].vValue.bstrVal;
	}
	const GUID * GetGuid(_In_ ULONG iPropSet)
	{
		ATLASSERT(iPropSet < m_cUPropSet);
		return m_pUPropSet[iPropSet].pPropSet;
	}
	DWORD GetPropID(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		return m_pUPropSet[iPropSet].pUPropInfo[iProp].dwPropId;
	}
	VARTYPE GetExpectedVarType(
		_In_ ULONG iPropSet,
		_In_ ULONG iProp)
	{
		ATLASSERT((  (iPropSet < m_cUPropSet)	&& (iProp < m_pUPropSet[iPropSet].cUPropInfo) && (iProp < m_pUProp[iPropSet].cPropIds) ));
		return m_pUPropSet[iPropSet].pUPropInfo[iProp].VarType;
	}
	_Success_(return == S_OK) virtual HRESULT GetIndexofPropSet(
		_In_ const GUID* pPropSet,
		_Out_ ULONG* pulCurSet)
	{
		ATLENSURE_RETURN(pPropSet && pulCurSet);

		for(ULONG ul=0; ul<m_cUPropSet; ul++)
		{
			if( *pPropSet == *(m_pUPropSet[ul].pPropSet) )
			{
				*pulCurSet = ul;
				return S_OK;
			}
		}
		return S_FALSE;
	}

	virtual HRESULT OnPropertyChanged(
		_In_ ULONG /*iCurSet*/,
		_Inout_ DBPROP* pDBProp)
	{
		// Prevent Level 4 warnings.
		UNREFERENCED_PARAMETER(pDBProp);
#ifndef _ATL_IF_EXISTS_FALLBACK
if constexpr(Has_CreateAccessor<T>::value)
#else
__if_exists(T::CreateAccessor)
#endif
{
		ATLENSURE_RETURN(pDBProp != NULL);
		DWORD dwPropertyID = pDBProp->dwPropertyID;
		CComVariant var = pDBProp->vValue;

		switch(dwPropertyID)
		{
		case DBPROP_IRowsetLocate:
		case DBPROP_LITERALBOOKMARKS:
		case DBPROP_ORDEREDBOOKMARKS:
			{
				CDBPropSet set(DBPROPSET_ROWSET);
				CComVariant newVar;
				bool bVal;
				if (var.boolVal == ATL_VARIANT_TRUE)
					bVal = true;
				else
					bVal = false;

				newVar = bVal;

				// Set the bookmarks property as they are chained.  We also need to
				// turn off IRowsetLocate if we're setting BOOKMARKS to false (see
				// the DBPROP_BOOKMARKS case statement.
				if (var.boolVal == ATL_VARIANT_FALSE)
					SetPropValue(&DBPROPSET_ROWSET, DBPROP_IRowsetLocate, &newVar);

				SetPropValue(&DBPROPSET_ROWSET, DBPROP_BOOKMARKS, &newVar);

				// If you set IRowsetLocate to true, then the rowset can
				// handle backward scrolling
				if (dwPropertyID == DBPROP_IRowsetLocate)
					SetPropValue(&DBPROPSET_ROWSET, DBPROP_CANSCROLLBACKWARDS, &newVar);

				return S_OK;
			}
			break;

		case DBPROP_IRowsetScroll:
			{
				CDBPropSet set(DBPROPSET_ROWSET);

				if (var.boolVal == ATL_VARIANT_TRUE)
				{
					if(!set.AddProperty(DBPROP_IRowsetLocate, true))
					{
						return E_FAIL;
					}
					if(!set.AddProperty(DBPROP_BOOKMARKS, true))
					{
						return E_FAIL;
					}
					if(!set.AddProperty(DBPROP_CANSCROLLBACKWARDS, true))
					{
						return E_FAIL;
					}
				}

				const GUID* ppGuid[1];
				ppGuid[0] = &DBPROPSET_ROWSET;

				return SetProperties(0, 1, &set, 1, ppGuid);
			}
			break;

		case DBPROP_BOOKMARKS:
			{
				if (var.boolVal == ATL_VARIANT_FALSE)
				{
					// Since we support bookmarks, if someone sets DBPROP_BOOKMARKS
					// to ATL_VARIANT_FALSE and DBPROP_IRowsetLocate is ATL_VARIANT_TRUE,
					// then we should return an error.  This is an invalid
					// combination of properties.
					CComVariant locateVar;
					HRESULT hr = GetPropValue(&DBPROPSET_ROWSET,
						DBPROP_IRowsetLocate, &locateVar);
					if (SUCCEEDED(hr) && locateVar.boolVal == ATL_VARIANT_TRUE)
					{
						// If the DBPROP_BOOKMARKS is set to required, return
						// DB_E_ERRORSOCCURRED.  Otherwise, set it to
						// DB_S_ERRORSOCCURRED.  Note, we won't reset the property
						// since setting IRowsetLocate to ATL_VARIANT_TRUE will
						// set DBPROP_BOOKMARKS to ATL_VARIANT_TRUE
						pDBProp->dwStatus = DBPROPSTATUS_CONFLICTING;

						if (pDBProp->dwOptions == DBPROPOPTIONS_REQUIRED)
							return DB_E_ERRORSOCCURRED;
						else
							return DB_S_ERRORSOCCURRED;
					}
				}
			}
			break;

		// This code should only be included if you are a command or rowset.
		case DBPROP_UPDATABILITY:
			{
				if (var.lVal != 0)
				{
					CComVariant changeVar;
					HRESULT hr = GetPropValue(&DBPROPSET_ROWSET,
						DBPROP_IRowsetChange, &changeVar);
					if (FAILED(hr) || changeVar.boolVal == ATL_VARIANT_FALSE)
					{
						// It doesn't make sense to set DBPROP_UPDATABILITY
						// if DBPROP_IRowsetChange is FALSE (i.e. no updates)
						pDBProp->dwStatus = DBPROPSTATUS_CONFLICTING;

						if (pDBProp->dwOptions == DBPROPOPTIONS_REQUIRED)
							return DB_E_ERRORSOCCURRED;
						else
							return DB_S_ERRORSOCCURRED;
					}
				}
			}

			// Update the IAccessor::m_bIsChangeable flag as necessary

			// Hold off on this for now, it appears to be causing a GPF becuase
			// we're writing this off the session.
//			pT->m_bIsChangeable = (var.iVal & DBPROPVAL_UP_INSERT);
			break;

		case DBPROP_IRowsetUpdate:
			{
				// Set the DBPROP_IRowsetChange and DBPROP_IRowsetUpdate flags.
				// Setting DBPROP_IRowsetUpdate to true sets DBPROP_IRowsetChange
				// to true

				if (var.boolVal == ATL_VARIANT_TRUE)
				{
					CComVariant changeVar(true);
					CDBPropSet set(DBPROPSET_ROWSET);
					if(!set.AddProperty(DBPROP_IRowsetChange, changeVar))
					{
						return E_FAIL;
					}

					const GUID* ppGuid[1];
					ppGuid[0] = &DBPROPSET_ROWSET;

					return SetProperties(0, 1, &set, 1, ppGuid);
				}
			}
			break;

		default:
			break;
		}
}

		return S_OK;
	}

	virtual HRESULT	InitUPropSetsSupported()
	{
		return InternalInitUPropSetsSupported(T::_GetPropSet);
	}

	HRESULT GetIndexOfPropertyInSet(
		_In_ const GUID* pPropSet,
		_In_ DBPROPID dwPropertyId,
		_Out_ ULONG* piCurPropId,
		_Out_ ULONG* piCurSet)
	{
		HRESULT hr = GetIndexofPropSet(pPropSet, piCurSet);
		if (hr == S_FALSE)
			return hr;
		UPROPINFO* pUPropInfo = m_pUPropSet[*piCurSet].pUPropInfo;
		for(ULONG ul=0; ul<m_pUPropSet[*piCurSet].cUPropInfo; ul++)
		{
			if( dwPropertyId == pUPropInfo[ul].dwPropId )
				*piCurPropId = ul;
			return S_OK;
		}

		return S_FALSE;
	}
	HRESULT SetSupportedBit(
		_In_ const GUID* pPropSet,
		_In_ DBPROPID dwPropertyId)
	{
		ULONG iCurPropId, iCurSet;

		if (GetIndexOfPropertyInSet(pPropSet, dwPropertyId, &iCurPropId, &iCurSet) == S_OK)
		{
			m_rgdwSupported[iCurSet * m_cElemPerSupported] |= 1 << iCurPropId;
			return S_OK;
		}
		return S_FALSE;
	}

	HRESULT ClearSupportedBit(
		_In_ const GUID* pPropSet,
		_In_ DBPROPID dwPropertyId)
	{
		ULONG iCurPropId, iCurSet;

		if (GetIndexOfPropertyInSet(pPropSet, dwPropertyId, &iCurPropId, &iCurSet) == S_OK)
		{
			m_rgdwSupported[iCurSet * m_cElemPerSupported] &= ~( 1 << iCurPropId);
			return S_OK;
		}
		return S_FALSE;
	}

	HRESULT TestSupportedBit(
		_In_ const GUID* pPropSet,
		_In_ DBPROPID dwPropertyId,
		_Out_ bool& bSet)
	{
		ULONG iCurPropId, iCurSet;

		if (GetIndexOfPropertyInSet(pPropSet, dwPropertyId, &iCurPropId, &iCurSet) == S_OK)
		{
			bSet = (m_rgdwSupported[iCurSet * m_cElemPerSupported] & ( 1 << iCurPropId)) != 0;
			return S_OK;
		}
		return S_FALSE;
	}
	void CopyPropsInError(_Out_ DWORD* rgdwSupported)
	{
		Checked::memcpy_s(rgdwSupported, m_cUPropSet * m_cElemPerSupported * sizeof(DWORD),
			m_rgdwPropsInError, m_cUPropSet * m_cElemPerSupported * sizeof(DWORD));
	}
};

// IDBPropertiesImpl
// IDBProperties <- IUnknown
template <class T>
class ATL_NO_VTABLE IDBPropertiesImpl :
	public IDBProperties,
	public CUtlProps<T>
{
public:
	STDMETHOD(GetProperties)(
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) const DBPROPIDSET rgPropertySets[],
		_Out_ ULONG *pcProperties,
		_Outptr_result_buffer_maybenull_(*pcProperties) DBPROPSET **prgProperties)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBPropertiesImpl::GetProperties\n"));
		T* pT = (T*)this;

		// You can get PROPERTIESINERROR on IDBProperties::GetProperties so do the
		// appropriate argument checking for it.
		this->m_dwFlags |= ARGCHK_PROPERTIESINERROR;
		HRESULT hr = this->GetPropertiesArgChk(cPropertySets, rgPropertySets, pcProperties, prgProperties);
		if (FAILED(hr))
			return hr;

		if (SUCCEEDED(hr))
		{
			// To allow user defined property groups, we'll scan the property
			// sets.

			ULONG ulPropSets = 0;
			ULONG ulPropElems = 0;
			ULONG ulPropInits = 0;
			UPROPSET* pSetA = NULL;
			UPROPSET* pSetTemp = NULL;
			ULONG l=0;
			ULONG cSets = (ULONG)(ULONG_PTR)pT->_GetPropSet(NULL, &ulPropElems);

			CTempBuffer<UPROPSET> tmpBuffer;
			pSetA = tmpBuffer.Allocate(cSets);
			if (pSetA == NULL)
				return E_OUTOFMEMORY;  // We shouldn't get this but...
			pSetTemp = pT->_GetPropSet(&ulPropSets, &ulPropElems, pSetA);

			typedef const GUID* PCGUID;

			if (pT->m_dwStatus & DSF_INITIALIZED)
			{
				for(l=0; l<cSets; l++)
				{
					if (pSetTemp[l].bIsChained != true)
						ulPropInits++;
				}

				CTempBuffer<PCGUID> tmpBuffer2;
				PCGUID* ppGuid = tmpBuffer2.Allocate(ulPropInits);
				if (ppGuid == NULL)
					return E_OUTOFMEMORY;

				ulPropInits = 0;
				for (l=0; l<cSets; l++)
				{
					if (pSetTemp[l].bIsChained != true)
						ppGuid[ulPropInits++] = pSetTemp[l].pPropSet;
				}

				hr = CUtlProps<T>::GetProperties(cPropertySets, rgPropertySets,
							pcProperties, prgProperties, ulPropInits, ppGuid);
			}
			else
			{
				// Only pass through initialization groups
				for(l=0; l<cSets; l++)
				{
					if (IsEqualGUID(*pSetTemp[l].pPropSet, DBPROPSET_DBINIT) ||
						pSetTemp[l].dwFlags & CUtlPropsBase::EnumUPropSetFlags::UPROPSET_USERINIT)
						ulPropInits++;
				}
				CTempBuffer<PCGUID> tmpBuffer2;
				PCGUID* ppGuid = tmpBuffer2.Allocate(ulPropInits);
				if (ppGuid == NULL)
					return E_OUTOFMEMORY;

				ulPropInits = 0;
				for(l=0; l<cSets; l++)
				{
					if (IsEqualGUID(*pSetTemp[l].pPropSet, DBPROPSET_DBINIT) ||
						pSetTemp[l].dwFlags & CUtlPropsBase::EnumUPropSetFlags::UPROPSET_USERINIT)
						ppGuid[ulPropInits++] = pSetTemp[l].pPropSet;
				}

				hr = CUtlProps<T>::GetProperties(cPropertySets, rgPropertySets,
							pcProperties, prgProperties, ulPropInits, ppGuid);
			}
		}

		this->m_dwFlags &= ~ARGCHK_PROPERTIESINERROR;
		return hr;
	}

ATLPREFAST_SUPPRESS(6387)
	STDMETHOD(GetPropertyInfo)(
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) const DBPROPIDSET rgPropertySets[],
		_Out_ ULONG *pcPropertyInfoSets,
		_Outptr_result_buffer_maybenull_(*pcPropertyInfoSets) DBPROPINFOSET **prgPropertyInfoSets,
		_Deref_opt_out_z_ OLECHAR **ppDescBuffer)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBPropertiesImpl::GetPropertyInfo\n"));
		T* pT = static_cast<T*>(this);
		typename T::ObjectLock cab(pT);

		if (pT->m_pCUtlPropInfo == NULL)
		{
			// Go ahead and create the m_pCUtlPropInfo but do not change the
			// Initialized status of the provider (see IDBInitialize::Initialize).
			ATLTRACE(atlTraceDBProvider, 2, _T("m_pCUtlPropInfo == NULL\n"));
			delete pT->m_pCUtlPropInfo;
			pT->m_pCUtlPropInfo = NULL;
			pT->m_pCUtlPropInfo = _ATL_NEW CUtlPropInfo<T>();
			if (pT->m_pCUtlPropInfo == NULL)
			{
				ATLTRACE(atlTraceDBProvider, 0, _T("IDBProperties::GetPropertyInfo Error : OOM\n"));
				return E_OUTOFMEMORY;
			}
			HRESULT hr = pT->m_pCUtlPropInfo->FInit();
			if (hr != S_OK)
			{
				delete pT->m_pCUtlPropInfo;
				pT->m_pCUtlPropInfo = NULL;
				return E_FAIL;
			}
		}

		// Initialize
		if( pcPropertyInfoSets )
			*pcPropertyInfoSets = 0;
		if( prgPropertyInfoSets )
			*prgPropertyInfoSets = NULL;
		if( ppDescBuffer )
			*ppDescBuffer = NULL;

		// Check Arguments
		if( ((cPropertySets > 0) && !rgPropertySets) ||
			!pcPropertyInfoSets || !prgPropertyInfoSets )
			return E_INVALIDARG;

		// New argument check for > 1 cPropertyIDs and NULL pointer for
		// array of property ids.
		const DWORD SPECIAL_GROUP		= 1;
		const DWORD SPECIAL_SINGLE		= 2;
		const DWORD SPECIALS			= SPECIAL_GROUP | SPECIAL_SINGLE;
		DWORD dwSpecial = 0;
		for(ULONG ul=0; ul<cPropertySets; ul++)
		{
			if( (rgPropertySets[ul].guidPropertySet == DBPROPSET_DATASOURCEALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_DATASOURCEINFOALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_DBINITALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_SESSIONALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_ROWSETALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_COLUMNALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_CONSTRAINTALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_INDEXALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_TABLEALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_TRUSTEEALL) ||
				(rgPropertySets[ul].guidPropertySet == DBPROPSET_VIEWALL))
				dwSpecial |= SPECIAL_GROUP;
			else
				dwSpecial |= SPECIAL_SINGLE;

			// Check for property sets containing both singles and groups
			// Check for cPropertyIDs != 0 & rgPropertyIDs == NULL
			if( (dwSpecial == SPECIALS) ||
				(rgPropertySets[ul].cPropertyIDs && !(rgPropertySets[ul].rgPropertyIDs)) )
				return E_INVALIDARG;
		}

		if (pT->m_dwStatus & DSF_INITIALIZED)
			return pT->m_pCUtlPropInfo->GetPropertyInfo(cPropertySets, rgPropertySets,
											  pcPropertyInfoSets, prgPropertyInfoSets,
											  ppDescBuffer, true);
		else
			return pT->m_pCUtlPropInfo->GetPropertyInfo(cPropertySets, rgPropertySets,
											  pcPropertyInfoSets, prgPropertyInfoSets,
											  ppDescBuffer, false, &DBPROPSET_DBINITALL);

	}
ATLPREFAST_UNSUPPRESS()

	STDMETHOD(SetProperties)(
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) DBPROPSET rgPropertySets[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBPropertiesImpl::SetProperties\n"));
		HRESULT	hr;
		CAutoVectorPtr < DBPROPSET > pdbPropSet;
		ULONG iProp;
		typedef const GUID* PCGUID;
		T* pT = static_cast<T*>(this);

		// Quick return if the Count of Properties is 0
		if( cPropertySets == 0 )
			return S_OK;

		// Determine how many sets are in the current map
		typename T::ObjectLock lock(pT);
		UPROPSET* pSetA = NULL;
		UPROPSET* pSetTemp = NULL;
		ULONG l=0;
		ULONG ulPropSets = 0;
		ULONG ulPropElems = 0;
		ULONG ulPropInits = 0;
		ULONG cSets = (ULONG)(ULONG_PTR)pT->_GetPropSet(NULL, &ulPropElems);
		ULONG iNewSet, iSet;

		CTempBuffer<UPROPSET> tmpBuffer;
		pSetA = tmpBuffer.Allocate(cSets);
		if (pSetA == NULL)
			return E_OUTOFMEMORY;  // We shouldn't get this but...

		pSetTemp = pT->_GetPropSet(&ulPropSets, &ulPropElems, pSetA);

		hr = CUtlProps<T>::SetPropertiesArgChk(cPropertySets, rgPropertySets);
		if(SUCCEEDED(hr))
		{
			// We need to handle the DBINIT properties specially after being initialized.
			// - they should be treated as NOTSETTABLE at this point.
			if( pT->m_dwStatus & DSF_INITIALIZED )
			{
				ATLASSERT(cPropertySets);

				BOOL fFoundDBINIT = FALSE;

				// Allocate a DBPROPSET structure of equal size
				pdbPropSet.Allocate( cPropertySets );
				if( pdbPropSet == NULL )
					return E_OUTOFMEMORY;

				for(iNewSet=0,iSet=0; iSet<cPropertySets; iSet++)
				{
					// Remove any DBPROPSET_DBINIT values and mark them all
					// as not settable
					if( (rgPropertySets[iSet].guidPropertySet == DBPROPSET_DBINIT))
					{
						fFoundDBINIT = TRUE;
						for(iProp=0; iProp<rgPropertySets[iSet].cProperties; iProp++)
							rgPropertySets[iSet].rgProperties[iProp].dwStatus = DBPROPSTATUS_NOTSETTABLE;
					}
					else
					{
						// If not DBPROPSET_DBINIT then copy the DBPROPSET values
						pdbPropSet[iNewSet++] = rgPropertySets[iSet];
					}
				}

				// If we have no propertyset to pass on to the property handler, we
				// can exit
				if( iNewSet == 0 )
				{
					return DB_E_ERRORSOCCURRED;
				}

				for(l=0; l<cSets; l++)
				{
					if (pSetTemp[l].bIsChained != true)
						ulPropInits++;
				}

				CTempBuffer<PCGUID> tmpBuffer2;
				PCGUID* ppGuid = tmpBuffer2.Allocate(ulPropInits);
				if (ppGuid == NULL)
					return E_OUTOFMEMORY;

				ulPropInits = 0;
				for (l=0; l<cSets; l++)
				{
					if (pSetTemp[l].bIsChained != true)
						ppGuid[ulPropInits++] = pSetTemp[l].pPropSet;
				}

				hr = CUtlProps<T>::SetProperties(0, iNewSet, pdbPropSet, ulPropInits, ppGuid);

				// If we have determined that one of the property sets was DBINIT, we may
				// need to fixup the returned hr value.
				if( fFoundDBINIT && SUCCEEDED(hr))
					hr = DB_S_ERRORSOCCURRED;
			}
			else
			{
				// Note that m_pCUtlProps knows about initialization,
				// so we don't have to here. Only pass DBPROPSET_DBINIT or custom
				// initialization groups

				for(l=0; l<cSets; l++)
				{
					if (IsEqualGUID(*pSetTemp[l].pPropSet, DBPROPSET_DBINIT) ||
						pSetTemp[l].dwFlags & CUtlPropsBase::EnumUPropSetFlags::UPROPSET_USERINIT)
						ulPropInits++;
				}

				CTempBuffer<PCGUID> tmpBuffer2;
				PCGUID* ppGuid = tmpBuffer2.Allocate(ulPropInits);
				if (ppGuid == NULL)
					return E_OUTOFMEMORY;

				ulPropInits = 0;
				for(l=0; l<cSets; l++)
				{
					if (IsEqualGUID(*pSetTemp[l].pPropSet, DBPROPSET_DBINIT) ||
						pSetTemp[l].dwFlags & CUtlPropsBase::EnumUPropSetFlags::UPROPSET_USERINIT)
						ppGuid[ulPropInits++] = pSetTemp[l].pPropSet;
				}

				hr = CUtlProps<T>::SetProperties(0, cPropertySets, rgPropertySets,
						ulPropInits, ppGuid);
			}
		}

		return hr;
	}
};

#define BEGIN_SCHEMA_MAP(SchemaClass) \
	typedef SchemaClass _SchemaClass; \
	HRESULT _SchemaSupport( \
		_Outptr_opt_result_maybenull_ GUID** ppGuid, \
		_Inout_opt_ IUnknown *pUnkOuter, \
		_In_ REFIID rguidSchema, \
		_In_ ULONG cRestrictions, \
		_In_reads_opt_(cRestrictions) const VARIANT rgRestrictions[], \
		_In_ REFIID riid, \
		_In_ ULONG cPropertySets, \
		_In_reads_opt_(cPropertySets) DBPROPSET rgPropertySets[], \
		_Outptr_opt_result_maybenull_ IUnknown **ppRowset) \
	{ \
	int cGuids = 0; \
	HRESULT hr = S_OK; \
	if (ppGuid != NULL) \
		*ppGuid = NULL;

#define SCHEMA_ENTRY(guid, rowsetClass) \
	if (ppGuid != NULL && SUCCEEDED(hr)) \
	{ \
		cGuids++; \
		*ppGuid = ATL::AtlSafeRealloc<GUID, ATL::CComAllocator>(*ppGuid, cGuids); \
		hr = (*ppGuid == NULL) ? E_OUTOFMEMORY : S_OK; \
		if (SUCCEEDED(hr)) \
			(*ppGuid)[cGuids - 1] = guid; \
		else \
			return hr; \
	} \
	else \
	{ \
		if (InlineIsEqualGUID(guid, rguidSchema)) \
		{ \
			rowsetClass* pRowset = NULL; \
			hr = CheckRestrictions(rguidSchema, cRestrictions, rgRestrictions); \
			if (FAILED(hr)) \
				return E_INVALIDARG; \
			hr = CreateSchemaRowset(pUnkOuter, cRestrictions, \
							   rgRestrictions, riid, cPropertySets, \
							   rgPropertySets, ppRowset, pRowset); \
			return hr; \
		} \
	}

#define END_SCHEMA_MAP() \
		if (ppGuid != NULL) \
			return hr; \
		return E_INVALIDARG; \
	}


template <class SessionClass>
class  ATL_NO_VTABLE IDBSchemaRowsetImpl:
	public IDBSchemaRowset
{
public:
	OUT_OF_LINE HRESULT InternalCreateSchemaRowset(
		_In_opt_ IUnknown *pUnkOuter,
		_In_ ULONG cRestrictions,
		_In_reads_opt_(cRestrictions) const VARIANT rgRestrictions[],
		_In_ REFIID riid,
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) DBPROPSET rgPropertySets[],
		_Outptr_opt_ IUnknown** ppRowset,
		_Inout_ IUnknown* pUnkThis,
		_Inout_ CUtlPropsBase* pProps,
		_Inout_opt_ IUnknown* pUnkSession)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBSchemaRowsetImpl::InternalCreateSchemaRowset\n"));
		UNREFERENCED_PARAMETER(cRestrictions);
		UNREFERENCED_PARAMETER(rgRestrictions);

		HRESULT hr, hrProps = S_OK;
		if (ppRowset != NULL)
			*ppRowset = NULL;
		if ((pUnkOuter != NULL) && !InlineIsEqualUnknown(riid))
			return DB_E_NOAGGREGATION;
		CComPtr<IUnknown> spUnk;
		hr = pUnkThis->QueryInterface(__uuidof(IUnknown), (void**)&spUnk);
		if (FAILED(hr))
			return hr;

		hr = pProps->FInit();
		if (FAILED(hr))
			return hr;
		hr = pProps->SetPropertiesArgChk(cPropertySets, rgPropertySets);
		if (FAILED(hr))
			return hr;
		const GUID* ppGuid[1];
		ppGuid[0] = &DBPROPSET_ROWSET;

		// Call SetProperties.  The true in the last parameter indicates
		// the special behavior that takes place on rowset creation (i.e.
		// it succeeds as long as any of the properties were not marked
		// as DBPROPS_REQUIRED.

		hrProps = pProps->SetProperties(0, cPropertySets, rgPropertySets,
											1, ppGuid, true);
		if (FAILED(hrProps))
			return hrProps;

		CComQIPtr<IObjectWithSite> spSite = spUnk;
		ATLASSERT(spSite != NULL);
		hr = spSite->SetSite(pUnkSession);
		if (FAILED(hr))
			return hr;
		if (InlineIsEqualGUID(riid, IID_NULL))
			return E_NOINTERFACE;
		if(ppRowset)
		{
			hr = spUnk->QueryInterface(riid, (void**)ppRowset);
		}
		if (FAILED(hr))
		{
			if(ppRowset)
			{
				*ppRowset = NULL;
			}
			return hr;
		}
		return (hrProps == DB_S_ERRORSOCCURRED) ? hrProps : hr;
	}

ATLPREFAST_SUPPRESS(6387)
	template <class SchemaRowsetClass>
	HRESULT CreateSchemaRowset(
		_Inout_opt_ IUnknown *pUnkOuter,
		_In_ ULONG cRestrictions,
		_In_reads_opt_(cRestrictions) const VARIANT rgRestrictions[],
		_In_ REFIID riid,
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) DBPROPSET rgPropertySets[],
		_Outptr_opt_ IUnknown** ppRowset,
		_Inout_ SchemaRowsetClass*& pSchemaRowset)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBSchemaRowsetImpl::CreateSchemaRowset\n"));

		HRESULT hrProps, hr = S_OK;
		CComPolyObject<SchemaRowsetClass>* pPolyObj;
		SessionClass* pT = (SessionClass*) this;
		if (FAILED(hr = CComPolyObject<SchemaRowsetClass>::CreateInstance(pUnkOuter, &pPolyObj)))
			return hr;
		pSchemaRowset = &(pPolyObj->m_contained);
		CComPtr<IUnknown> spOuterUnk;
		(pT->GetUnknown())->QueryInterface(__uuidof(IUnknown), (void**)&spOuterUnk);
		SchemaRowsetClass* myPT = (SchemaRowsetClass*) pSchemaRowset;
		hr = InternalCreateSchemaRowset(pUnkOuter, cRestrictions, rgRestrictions,
										riid, cPropertySets, rgPropertySets, ppRowset,
										pPolyObj, myPT, spOuterUnk);
		// Ref the created COM object and Auto release it on failure
		if (FAILED(hr))
		{
			delete pPolyObj;
			return hr;
		}

		hrProps = hr;
		// Get a pointer to the Rowset instance
		DBROWCOUNT cRowsAffected;
		hr = pSchemaRowset->Execute(&cRowsAffected, cRestrictions, rgRestrictions);
		if (FAILED(hr))
			return hr;

		return (hrProps == DB_S_ERRORSOCCURRED) ? hrProps : hr;
	}
ATLPREFAST_UNSUPPRESS()

	void SetRestrictions(
		_In_ ULONG cRestrictions,
		_In_opt_ GUID* /*rguidSchema*/,
		_In_reads_(cRestrictions) ULONG* rgRestrictions)
	{
		memset(rgRestrictions, 0, sizeof(ULONG) * cRestrictions);
	}

	STDMETHOD(GetSchemas)(
		_Out_ ULONG* pcSchemas,
		_Outptr_ GUID** prgSchemas,
		_Outptr_opt_ ULONG** prgRest)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBSchemaRowsetImpl::GetSchemas\n"));
		if (pcSchemas != NULL)
			*pcSchemas = 0;
		if (prgSchemas != NULL)
			*prgSchemas = NULL;
		if (pcSchemas == NULL || prgSchemas == NULL)
			return E_INVALIDARG;

		SessionClass* pT = (SessionClass*)this;

		HRESULT hr = pT->_SchemaSupport(prgSchemas, NULL, GUID_NULL, 0,
										NULL, GUID_NULL, 0, NULL, NULL);
		if (FAILED(hr))
			return hr;

		CComPtr<IMalloc> spMalloc;
		hr = CoGetMalloc(1, &spMalloc);
		if (FAILED(hr))
		{
			CoTaskMemFree(*prgSchemas);
			*prgSchemas = NULL;
			return hr;
		}
		*pcSchemas = (ULONG)(spMalloc->GetSize(*prgSchemas) / sizeof(GUID));

		if (prgRest != NULL)
		{
			// The OLE DB spec states that if prgRest == NULL not to return array
			// but it also says that is E_INVALIDARG, so doing first
			size_t nBytes=0;
			if( FAILED(hr=::ATL::AtlMultiply(&nBytes, sizeof(ULONG), static_cast<size_t>(*pcSchemas))))
			{
				spMalloc->Free(*prgSchemas);
				*prgSchemas = NULL;
				return hr;
			}
			*prgRest = (ULONG*) spMalloc->Alloc(nBytes);
			if (*prgRest == NULL)
			{
				spMalloc->Free(*prgSchemas);
				*prgSchemas = NULL;
				return E_OUTOFMEMORY;
			}
			pT->SetRestrictions(*pcSchemas, *prgSchemas, *prgRest);
		}
		return hr;
	}
	STDMETHOD(GetRowset)(
		_Inout_opt_ IUnknown *pUnkOuter,
		_In_ REFGUID rguidSchema,
		_In_ ULONG cRestrictions,
		_In_reads_(cRestrictions) const VARIANT rgRestrictions[],
		_In_ REFIID riid,
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) DBPROPSET rgPropertySets[],
		_Outptr_ IUnknown **ppRowset)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBSchemaRowsetImpl::GetRowset\n"));
		SessionClass* pT = (SessionClass*)this;

		// You must specify a rowset pointer per the specification
		if (ppRowset == NULL || (cRestrictions > 0 && rgRestrictions == NULL) ||
			(cPropertySets > 0 && rgPropertySets == NULL))
			return E_INVALIDARG;

		if (rgPropertySets != NULL)
		{
			for (ULONG ulSet = 0; ulSet < cPropertySets; ulSet++)
			{
				if (rgPropertySets[ulSet].cProperties > 0 &&
					rgPropertySets[ulSet].rgProperties == NULL)
					return E_INVALIDARG;
			}
		}

		return  pT->_SchemaSupport(NULL, pUnkOuter, rguidSchema, cRestrictions,
								   rgRestrictions, riid, cPropertySets,
								   rgPropertySets, ppRowset);

	}

	HRESULT CheckRestrictions(
		_In_ REFGUID rguidSchema,
		_In_ ULONG cRestrictions,
		_In_reads_opt_(cRestrictions) const VARIANT rgRestrictions[])
	{
		// Use this function to help check the validity of restrictions
		// against a schema rowset.
		const VARTYPE rgRestrictionTypes[3][4] = {
			{ VT_BSTR, VT_BSTR, VT_BSTR, VT_BSTR },		// DBSCHEMA_TABLES
			{ VT_BSTR, VT_BSTR, VT_BSTR, VT_BSTR },		// DBSCHEMA_COLUMNS
			{ VT_UI2, VT_BOOL, VT_EMPTY, VT_EMPTY }};	// DBSCHEMA_PROVIDER_TYPES
		const ULONG ulMaxRestrictions[3] = { 4, 4, 2};

		ULONG ulRes = 0;
		ULONG ulType = 3;

		if (InlineIsEqualGUID(rguidSchema, DBSCHEMA_TABLES))
			ulType = 0;
		else if (InlineIsEqualGUID(rguidSchema, DBSCHEMA_COLUMNS))
			ulType = 1;
		else if (InlineIsEqualGUID(rguidSchema, DBSCHEMA_PROVIDER_TYPES))
			ulType = 2;

		if (ulType < 3)		// I.E. we found one of our supported rowsets
		{
			ULONG ulCurrentRestrictions = 0x00;
			ULONG ulCurrentMask = 0x01;

			// Ask the provider's session object for its list of restrictions
			SessionClass* pT = (SessionClass*)this;
			pT->SetRestrictions(1, (GUID*)&rguidSchema, &ulCurrentRestrictions);

			ATLASSERT(ulType >= 0 && ulType < 3);
			// We allow VT_EMPTY through in case the consumer wanted to ignore this
			// restriction (basically a way to pass in 'NULL').
			if (cRestrictions > ulMaxRestrictions[ulType])
				return E_INVALIDARG;

			for (ulRes = 0; ulRes < cRestrictions; ulRes++)
			{
				// Check for obviously invalid types
				if (rgRestrictions[ulRes].vt != rgRestrictionTypes[ulType][ulRes] &&
					rgRestrictions[ulRes].vt != DBTYPE_EMPTY &&
					rgRestrictions[ulRes].vt != VT_NULL )
					return E_INVALIDARG;

				// Check for restrictions the provider doesn't support.
				if (!(ulCurrentMask & ulCurrentRestrictions) &&
					(rgRestrictions[ulRes].vt != DBTYPE_EMPTY))
					return E_INVALIDARG;

				ulCurrentMask <<= 1;		// Increase mask by * 2;
			}
		}

		return S_OK;
	}
};

// IDBCreateCommandImpl
template <class T, class CommandClass>
class ATL_NO_VTABLE IDBCreateCommandImpl :
	public IDBCreateCommand
{
public:
ATLPREFAST_SUPPRESS(6387)
	STDMETHOD(CreateCommand)(
		_Inout_opt_ IUnknown *pUnkOuter,
		_In_ REFIID riid,
		_Outptr_ IUnknown** ppvCommand)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IDBCreateCommandImpl::CreateCommand\n"));
		T* pT = (T*) this;

		if (ppvCommand == NULL)
			return E_INVALIDARG;
		else
			*ppvCommand = NULL;

		HRESULT hr;
		CComPolyObject<CommandClass>* pCommand;

		// You can't QI for an interface other than IUnknown when aggregating
		// and creating the object.  You might ask for your own interface,
		// which would be bad.  Note, we return DB_E_NOAGGREGATION instead of
		// CLASS_E_NOAGGREGATION due to OLE DB constraints.
		if (pUnkOuter != NULL && !InlineIsEqualUnknown(riid))
			return DB_E_NOAGGREGATION;

		hr = CComPolyObject<CommandClass>::CreateInstance(pUnkOuter, &pCommand);
		if (FAILED(hr))
			return hr;

		ATLASSUME(pCommand != NULL);
		// Ref the created COM object and Auto release it on failure
		CComPtr<IUnknown> spUnk;
		hr = pCommand->QueryInterface(&spUnk);
		if (FAILED(hr))
		{
			delete pCommand; // must hand delete as it is not ref'd
			return hr;
		}
		ATLASSERT(pCommand->m_contained.m_spUnkSite == NULL);
		CComPtr<IUnknown> spOuterUnk;
		hr = pT->QueryInterface(__uuidof(IUnknown), (void**)&spOuterUnk);
		if (SUCCEEDED(hr))
		{
			hr = pCommand->m_contained.SetSite(spOuterUnk);
			if (SUCCEEDED(hr))
			{
				hr = pCommand->QueryInterface(riid, (void**)ppvCommand);
			}
		}
		return hr;
	}
ATLPREFAST_UNSUPPRESS()
};

// IGetDataSourceImpl
template <class T>
class ATL_NO_VTABLE IGetDataSourceImpl :
	public IGetDataSource
{
public:
	STDMETHOD(GetDataSource)(
		_In_ REFIID riid,
		_Outptr_ IUnknown **ppDataSource)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IGetDataSourceImpl::GetDataSource\n"));
		if (ppDataSource == NULL)
			return E_INVALIDARG;
		T* pT = (T*) this;
		ATLASSERT(pT->m_spUnkSite != NULL);
		return pT->m_spUnkSite->QueryInterface(riid, (void**)ppDataSource);
	}
};


// IOpenRowsetImpl
template <class SessionClass>
class IOpenRowsetImpl :
	public IOpenRowset
{
public:
ATLPREFAST_SUPPRESS(6387)
	template <class RowsetClass>
	HRESULT CreateRowset(
		_Inout_opt_ IUnknown* pUnkOuter,
		_In_opt_ DBID *pTableID,
		_In_opt_ DBID *pIndexID,
		_In_ REFIID riid,
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) DBPROPSET rgPropertySets[],
		_Outptr_ IUnknown** ppRowset,
		_In_ RowsetClass*& pRowsetObj)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IOpenRowsetImpl::CreateRowset\n"));

		HRESULT hr, hrProps = S_OK;
		if (ppRowset != NULL)
			*ppRowset = NULL;
		if ((pUnkOuter != NULL) && !InlineIsEqualUnknown(riid))
			return DB_E_NOAGGREGATION;
		CComPolyObject<RowsetClass>* pPolyObj;
		if (FAILED(hr = CComPolyObject<RowsetClass>::CreateInstance(pUnkOuter, &pPolyObj)))
			return hr;
		// Ref the created COM object and Auto release it on failure
		CComPtr<IUnknown> spUnk;
		hr = pPolyObj->QueryInterface(&spUnk);
		if (FAILED(hr))
		{
			delete pPolyObj; // must hand delete as it is not ref'd
			return hr;
		}
		// Get a pointer to the Rowset instance
		pRowsetObj = &(pPolyObj->m_contained);
		hr = pRowsetObj->FInit();
		if (FAILED(hr))
			return hr;
		hr = pRowsetObj->SetPropertiesArgChk(cPropertySets, rgPropertySets);
		if (FAILED(hr))
			return hr;

		const GUID* ppGuid[1];
		ppGuid[0] = &DBPROPSET_ROWSET;

		// Call SetProperties.  The true in the last parameter indicates
		// the special behavior that takes place on rowset creation (i.e.
		// it succeeds as long as any of the properties were not marked
		// as DBPROPS_REQUIRED.

		hrProps = pRowsetObj->SetProperties(0, cPropertySets, rgPropertySets,
											1, ppGuid, true);
		if (FAILED(hrProps))
			return hrProps;
		CComPtr<IUnknown> spOuterUnk;
		hr = ((SessionClass*)this)->QueryInterface(__uuidof(IUnknown), (void**)&spOuterUnk);
		if (FAILED(hr))
		{
			return hr;
		}

		hr = pRowsetObj->SetSite(spOuterUnk);
		if (FAILED(hr))
		{
			return hr;
		}

		hr = pRowsetObj->SetCommandText(pTableID, pIndexID);
		if (FAILED(hr))
			return hr;
		DBROWCOUNT cRowsAffected;

		// Check to make sure we set any 'post' properties based on the riid
		// requested.
		if (FAILED(pRowsetObj->OnInterfaceRequested(riid)))
			return hr;

		HRESULT hrExecute;
		if (FAILED(hrExecute = pRowsetObj->Execute(NULL, &cRowsAffected)))
			return hrExecute;
/*
		CComVariant varMaxRows;
		if (SUCCEEDED(pRowsetObj->GetPropValue(&DBPROPSET_ROWSET, DBPROP_MAXROWS, &varMaxRows)))
		{
			if( varMaxRows.lVal > 0 )
			{
				if( pRowsetObj->m_rgRowData.GetCount() > (size_t)varMaxRows.lVal )
				{
					pRowsetObj->m_rgRowData.RemoveAt( varMaxRows.lVal,
														pRowsetObj->m_rgRowData.GetCount() - varMaxRows.lVal );
				}
			}
		}
*/
		if (InlineIsEqualGUID(riid, IID_NULL))
		{
			return E_NOINTERFACE;
		}
		else
		{
			if (ppRowset == NULL)
				//return (hrProps == DB_S_ERRORSOCCURRED) ? DB_E_ERRORSOCCURRED : hr;
				return hrProps;
			hr = pPolyObj->QueryInterface(riid, (void**)ppRowset);

		}

		if (FAILED(hr))
		{
			*ppRowset = NULL;
			return hr;
		}

		return (hrProps == DB_S_ERRORSOCCURRED && hrExecute != DB_S_STOPLIMITREACHED) ? hrProps : hrExecute;
	}
ATLPREFAST_UNSUPPRESS()
};


// IColumnsInfoImpl
template <class T>
class ATL_NO_VTABLE IColumnsInfoImpl :
	public IColumnsInfo,
	public CDBIDOps
{
public:

	HRESULT CheckCommandText(_Inout_ IUnknown* pUnkThis)
	{
		HRESULT hr = E_FAIL;
		CComPtr<ICommandText> spText;
		if (SUCCEEDED(hr = pUnkThis->QueryInterface(__uuidof(ICommandText), (void**)&spText)))
		{
			LPOLESTR szCommand;
			hr = spText->GetCommandText(NULL, &szCommand);
			if (SUCCEEDED(hr))
				CoTaskMemFree(szCommand);
		}
		return hr;
	}
	OUT_OF_LINE HRESULT InternalGetColumnInfo(
		_Out_ DBORDINAL *pcColumns,
		_Outptr_result_buffer_(*pcColumns) ATLCOLUMNINFO** ppInfo)
	{
		ATLENSURE_RETURN(ppInfo != NULL);
		T* pT = (T*) this;
		if (pT->CheckCommandText(pT->GetUnknown()) == DB_E_NOCOMMAND)
			return DB_E_NOCOMMAND;
		*ppInfo = T::GetColumnInfo(pT, pcColumns);
		return S_OK;
	}

ATLPREFAST_SUPPRESS(6387)
	STDMETHOD(GetColumnInfo)(
		_Out_ DBORDINAL *pcColumns,
		_Outptr_ DBCOLUMNINFO **prgInfo,
		_Outptr_result_z_ OLECHAR **ppStringsBuffer)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IColumnsInfoImpl::GetColumnInfo\n"));
		if (pcColumns == NULL || prgInfo == NULL || ppStringsBuffer == NULL)
		{
			if (prgInfo != NULL)
				*prgInfo = NULL;
			if (ppStringsBuffer != NULL)
				*ppStringsBuffer = NULL;
			if (pcColumns != NULL)
				*pcColumns = NULL;
			return E_INVALIDARG;
		}

		// NULL out pointers in case of an error
		*prgInfo = NULL;
		*ppStringsBuffer = NULL;
		*pcColumns = 0;

		ATLCOLUMNINFO* pInfo;
		HRESULT hr = InternalGetColumnInfo(pcColumns, &pInfo);
		if (FAILED(hr))
			return hr;
		*prgInfo = (DBCOLUMNINFO*)::ATL::AtlCoTaskMemCAlloc(*pcColumns, static_cast<ULONG>(sizeof(DBCOLUMNINFO)));
		if (*prgInfo != NULL)
		{
			ULONG cwRequired;
			DBORDINAL iCol;
			for (iCol = 0, cwRequired = 0; iCol < *pcColumns; iCol++)
			{
				(*prgInfo)[iCol].pwszName = pInfo[iCol].pwszName;
				(*prgInfo)[iCol].pTypeInfo = pInfo[iCol].pTypeInfo;
				(*prgInfo)[iCol].iOrdinal = pInfo[iCol].iOrdinal;
				(*prgInfo)[iCol].dwFlags = pInfo[iCol].dwFlags;
				(*prgInfo)[iCol].ulColumnSize = pInfo[iCol].ulColumnSize;
				(*prgInfo)[iCol].wType = pInfo[iCol].wType;
				(*prgInfo)[iCol].bPrecision = pInfo[iCol].bPrecision;
				(*prgInfo)[iCol].bScale = pInfo[iCol].bScale;
				(*prgInfo)[iCol].columnid = pInfo[iCol].columnid;

				if (pInfo[iCol].pwszName)
				{
					cwRequired += static_cast<ULONG>(wcslen(pInfo[iCol].pwszName) + 1);
				}
			}
			*ppStringsBuffer = (OLECHAR*)::ATL::AtlCoTaskMemCAlloc(cwRequired, static_cast<ULONG>(sizeof(OLECHAR)));
			if (*ppStringsBuffer)
			{
				DBORDINAL iColStrings;
				size_t iOffset;
				for (iColStrings = 0, iOffset = 0; iColStrings < *pcColumns; iColStrings++)
				{
					if (pInfo[iColStrings].pwszName)
					{
						if(!ocscpy_s(*ppStringsBuffer + iOffset, cwRequired-iOffset, pInfo[iColStrings].pwszName))
						{
							return E_FAIL;
						}
						(*prgInfo)[iColStrings].pwszName = *ppStringsBuffer + iOffset;
						if ((pInfo[iColStrings].columnid.eKind == DBKIND_GUID_NAME ||
							 pInfo[iColStrings].columnid.eKind == DBKIND_NAME ||
							 pInfo[iColStrings].columnid.eKind == DBKIND_PGUID_NAME))
						{
							(*prgInfo)[iColStrings].columnid.uName.pwszName  = *ppStringsBuffer + iOffset;
						}
						iOffset += wcslen(*ppStringsBuffer + iOffset) + 1;
					}
				}
				return S_OK;
			}
			else
			{
				ATLTRACE(atlTraceDBProvider, 0, _T("Failed to allocate string buffer\n"));
				CoTaskMemFree(*prgInfo);
				*prgInfo = NULL;
				*pcColumns = 0;
				return E_OUTOFMEMORY;
			}
		}
		else
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("Failed to allocate ColumnInfo array\n"));
			*prgInfo = NULL;
			*pcColumns = 0;
			return E_OUTOFMEMORY;
		}
	}
ATLPREFAST_UNSUPPRESS()

	STDMETHOD(MapColumnIDs)(
		_In_ DBORDINAL cColumnIDs,
		_In_reads_(cColumnIDs) const DBID rgColumnIDs[],
		_Out_writes_(cColumnIDs) DBORDINAL rgColumns[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IColumnsInfoImpl::MapColumnIDs\n"));
		if ((cColumnIDs != 0 && rgColumnIDs == NULL) || rgColumns == NULL)
			return E_INVALIDARG;
		DBORDINAL cCols = 0;
		DBORDINAL cColsInError = 0;
		HRESULT hr = S_OK;
		ATLCOLUMNINFO* pInfo;
		for (DBORDINAL iColId = 0; iColId < cColumnIDs; iColId++)
		{
			hr = InternalGetColumnInfo(&cCols, &pInfo);
			if (hr == DB_E_NOCOMMAND)
				return hr;
			DBORDINAL iColMapCur = 0;
			BOOL bDone = FALSE;
			while(iColMapCur < cCols && !bDone)
			{
				hr = CompareDBIDs(&(pInfo[iColMapCur].columnid), &(rgColumnIDs[iColId]));
				bDone = (hr == S_OK || FAILED(hr));
				if (hr == S_OK)
					rgColumns[iColId] = pInfo[iColMapCur].iOrdinal;
				iColMapCur++;
			}
			if (!bDone || FAILED(hr))
			{
				rgColumns[iColId] = DB_INVALIDCOLUMN;
				cColsInError++;
			}

		}
		if (cColsInError > 0 && cColumnIDs == cColsInError)
			return DB_E_ERRORSOCCURRED;
		if (cColsInError > 0 && cColsInError < cColumnIDs)
			return DB_S_ERRORSOCCURRED;
		return S_OK;
	}
};

//IConvertTypeImpl
template <class T>
class ATL_NO_VTABLE IConvertTypeImpl :
	public IConvertType,
	public CConvertHelper
{
public:
	HRESULT InternalCanConvert(
		_In_ DBTYPE wFromType,
		_In_ DBTYPE wToType,
		_In_ DBCONVERTFLAGS dwConvertFlags,
		_In_ bool bIsCommand,
		_In_ bool bHasParamaters,
		_Inout_ IObjectWithSite* pSite)
	{

		// Determine if new 2.x flags are valid
		if((dwConvertFlags & ~(DBCONVERTFLAGS_ISLONG | DBCONVERTFLAGS_ISFIXEDLENGTH | DBCONVERTFLAGS_FROMVARIANT)) != DBCONVERTFLAGS_COLUMN
			&& (dwConvertFlags & ~(DBCONVERTFLAGS_ISLONG | DBCONVERTFLAGS_ISFIXEDLENGTH | DBCONVERTFLAGS_FROMVARIANT)) != DBCONVERTFLAGS_PARAMETER )
			return DB_E_BADCONVERTFLAG;

		// If the convert flags are for DBCONVERTFLAGS_FROMVARIANT, check to see
		// that the type is a variant type
		if (dwConvertFlags & DBCONVERTFLAGS_FROMVARIANT)
		{
			if ((wFromType == DBTYPE_BYTES) ||
				(wFromType == DBTYPE_STR) ||
				(wFromType == DBTYPE_WSTR) ||
				(wFromType == DBTYPE_NUMERIC) ||
				(wFromType == DBTYPE_UDT) ||
				(wFromType == DBTYPE_DBDATE) ||
				(wFromType == DBTYPE_DBTIME) ||
				(wFromType == DBTYPE_DBTIMESTAMP) ||
				(wFromType == DBTYPE_HCHAPTER) ||
				(wFromType == DBTYPE_PROPVARIANT) ||
				(wFromType == DBTYPE_VARNUMERIC))
				return DB_E_BADTYPE;
		}

		// Note, if the convert flag is either ISLONG or ISFIXEDLENGTH, then we should
		// make sure we are not dealing with an OLE DB 1.x provider.  However, since
		// we default to 2.x providers, we don't check this.  If you, change the
		// DBPROP_PROVIDEROLEDBVER property in the DATASOURCEINFO group, you need to
		// check the property value and return a DB_E_BADCONVERTFLAG if it is a 1.x
		// provider.

		// Do we have ISLONG on a fixed length data type?
		DBTYPE dbtype = (DBTYPE) (wFromType & ~(DBTYPE_BYREF|DBTYPE_VECTOR|DBTYPE_ARRAY|DBTYPE_RESERVED));
		if ((dwConvertFlags & DBCONVERTFLAGS_ISLONG) &&
			(dbtype != DBTYPE_WSTR && dbtype != DBTYPE_STR && dbtype != DBTYPE_BYTES && dbtype != DBTYPE_VARNUMERIC))
			return DB_E_BADCONVERTFLAG;

		DBCONVERTFLAGS dwBaseFlags = (DBCONVERTFLAGS)(dwConvertFlags & ~(DBCONVERTFLAGS_ISLONG | DBCONVERTFLAGS_ISFIXEDLENGTH | DBCONVERTFLAGS_FROMVARIANT));

		// Check if this should be an & or an ==
		if (dwBaseFlags == DBCONVERTFLAGS_PARAMETER)
		{
			// In the case where we are a rowset and ask for a parameter
			// conversion, return DB_E_BADCONVERTFLAG
			if (!bIsCommand)
				return DB_E_BADCONVERTFLAG;

			// In the case where we are a command and ask for a parameter
			// conversion and ICommandWithParameters is not supported, return
			// DB_E_BADCONVERTFLAG.  We used to return S_FALSE, but spec says
			// return DB_E_BADCONVERTFLAG if not supported.
			if (!bHasParamaters)
				return DB_E_BADCONVERTFLAG;
		}

		// If we deal with a command and the user asks for a conversion on a rowset
		// the DBPROP_ROWSETCONVERSIONSONCOMMAND must be supported and set to TRUE.
		if (bIsCommand && (dwBaseFlags == DBCONVERTFLAGS_COLUMN))
		{
			CDBPropIDSet set(DBPROPSET_DATASOURCEINFO);
			if(!set.AddPropertyID(DBPROP_ROWSETCONVERSIONSONCOMMAND))
			{
				return E_FAIL;
			}
			DBPROPSET* pPropSet = NULL;
			ULONG ulPropSet = 0;

			// Get a pointer into the session
			CComPtr<IGetDataSource> spDataSource;
			CComPtr<IDBProperties> spProps;

			// if any of these calls fail, we're either unable to retrieve the
			// property or it is unsupported.  Since the property is only on
			// the data source object, we use the IObjectWithSite interface to
			// get the session object and then the GetDataSource method to get
			// the data source object itself.
			if (FAILED(pSite->GetSite(__uuidof(IGetDataSource), (void**)&spDataSource)))
				return DB_E_BADCONVERTFLAG;
			if (FAILED(spDataSource->GetDataSource(__uuidof(IDBProperties),
				(IUnknown**)&spProps)))
				return DB_E_BADCONVERTFLAG;
			if (FAILED(spProps->GetProperties(1, &set, &ulPropSet, &pPropSet)))
				return DB_E_BADCONVERTFLAG;

			if (pPropSet != NULL)
			{
				CComVariant var = pPropSet->rgProperties[0].vValue;
				CoTaskMemFree(pPropSet->rgProperties);
				CoTaskMemFree(pPropSet);

				if (var.boolVal == ATL_VARIANT_FALSE)
					return DB_E_BADCONVERTFLAG;
			}
		}
		HRESULT hr = E_FAIL;
		if (m_spConvert != NULL)
		{
			hr = m_spConvert->CanConvert(wFromType, wToType);
			if (hr == E_INVALIDARG)
			{
				// Data Conversion library said the types specified were invalid
				// however, in OLE DB 2.X, the value for this changed from
				// E_INVALIDARG to S_FALSE
				hr = S_FALSE;
			}
		}
		return hr;
	}
	STDMETHOD(CanConvert)(
		_In_ DBTYPE wFromType,
		_In_ DBTYPE wToType,
		_In_ DBCONVERTFLAGS dwConvertFlags)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IConvertTypeImpl::CanConvert\n"));
		T* pT = (T*)this;
		return pT->InternalCanConvert(wFromType, wToType, dwConvertFlags, pT->m_bIsCommand, pT->m_bHasParamaters, pT);
	}
};

template <class T, class PropClass = T>
class ATL_NO_VTABLE ICommandPropertiesImpl :
	public ICommandProperties,
	public CUtlProps<PropClass>
{
public:
	typedef PropClass _PropClass;

	STDMETHOD(GetProperties)(
		_In_ const ULONG cPropertyIDSets,
		_In_reads_(cPropertyIDSets) const DBPROPIDSET rgPropertyIDSets[],
		_Out_ ULONG *pcPropertySets,
		_Outptr_result_buffer_maybenull_(*pcPropertySets) DBPROPSET **prgPropertySets)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("ICommandPropertiesImpl::GetProperties\n"));

		T* pT;
		pT = static_cast<T*>(this);
		this->m_dwFlags |= ARGCHK_PROPERTIESINERROR;
		HRESULT hr = this->GetPropertiesArgChk(cPropertyIDSets, rgPropertyIDSets, pcPropertySets, prgPropertySets);
		if(SUCCEEDED(hr))
		{
			// Scan property sets to allow user defined properties
			ULONG ulPropSets = 0;
			ULONG ulPropElems = 0;
			ULONG ulPropInits = 0;
			UPROPSET* pSetA = NULL;
			UPROPSET* pSetTemp = NULL;
			ULONG l=0;
			ULONG cSets = (ULONG)(ULONG_PTR)pT->_GetPropSet(NULL, &ulPropElems);

			CTempBuffer<UPROPSET> tmpBuffer;
			pSetA = tmpBuffer.Allocate(cSets);
			if (pSetA == NULL)
				return E_OUTOFMEMORY;  // We shouldn't get this but...
			pSetTemp = pT->_GetPropSet(&ulPropSets, &ulPropElems, pSetA);

			typedef const GUID* PCGUID;

			for(l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ulPropInits++;
			}

			CTempBuffer<PCGUID> tmpBuffer2;
			PCGUID* ppGuid = tmpBuffer2.Allocate(ulPropInits);
			if (ppGuid == NULL)
				return E_OUTOFMEMORY;
			ulPropInits = 0;
ATLPREFAST_SUPPRESS(6386)
			for (l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ppGuid[ulPropInits++] = pSetTemp[l].pPropSet;
			}
ATLPREFAST_UNSUPPRESS()
ATLPREFAST_SUPPRESS(6385)
			hr = CUtlProps<PropClass>::GetProperties(cPropertyIDSets,
					rgPropertyIDSets, pcPropertySets, prgPropertySets,
					ulPropInits, ppGuid);
ATLPREFAST_UNSUPPRESS()
		}
		this->m_dwFlags &= ~ARGCHK_PROPERTIESINERROR;
		return hr;

	}

	STDMETHOD(SetProperties)(
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) DBPROPSET rgPropertySets[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("ICommandPropertiesImpl::SetProperties\n"));
		T* pT = (T*)this;

		if (pT->m_cRowsetsOpen > 0)
			return DB_E_OBJECTOPEN;	// Don't allow property sets on an open rowset

		HRESULT hr = CUtlPropsBase::SetPropertiesArgChk(cPropertySets, rgPropertySets);
		if(SUCCEEDED(hr))
		{
			// Scan property sets to allow user defined properties
			ULONG ulPropSets = 0;
			ULONG ulPropElems = 0;
			ULONG ulPropInits = 0;
			UPROPSET* pSetA = NULL;
			UPROPSET* pSetTemp = NULL;
			ULONG l=0;
			ULONG cSets = (ULONG)(ULONG_PTR)pT->_GetPropSet(NULL, &ulPropElems);

			CTempBuffer<UPROPSET> tmpBuffer;
			pSetA = tmpBuffer.Allocate(cSets);
			if (pSetA == NULL)
				return E_OUTOFMEMORY;  // We shouldn't get this but...
			pSetTemp = pT->_GetPropSet(&ulPropSets, &ulPropElems, pSetA);

			typedef const GUID* PCGUID;

			for(l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ulPropInits++;
			}

			CTempBuffer<PCGUID> tmpBuffer2;
			PCGUID* ppGuid = tmpBuffer2.Allocate(ulPropInits);
			if (ppGuid == NULL)
				return E_OUTOFMEMORY;
			ulPropInits = 0;
ATLPREFAST_SUPPRESS(6386)
			for (l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ppGuid[ulPropInits++] = pSetTemp[l].pPropSet;
			}
ATLPREFAST_UNSUPPRESS()
ATLPREFAST_SUPPRESS(6385)
			hr = CUtlProps<PropClass>::SetProperties(0, cPropertySets,
					rgPropertySets, ulPropInits, ppGuid);
ATLPREFAST_UNSUPPRESS()
		}
		return hr;
	}
};

template <class T, class CommandBase = ICommand>
class ATL_NO_VTABLE ICommandImpl :
	public CommandBase
{
public:
	ICommandImpl()
	{
		m_bIsExecuting = FALSE;
		m_bCancelWhenExecuting = TRUE;
		m_bCancel = FALSE;
	}
	HRESULT CancelExecution()
	{
		T* pT = (T*)this;
		pT->Lock();
		m_bCancel = TRUE;
		pT->Unlock();
		return S_OK;
	}
	STDMETHOD(Cancel)()
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("ICommandImpl::Cancel\n"));
		HRESULT hr = S_OK;
		T* pT = (T*)this;

		if (m_bIsExecuting && m_bCancelWhenExecuting)
		{
			hr = pT->CancelExecution();
			return hr;
		}
		if (m_bIsExecuting && !m_bCancelWhenExecuting)
			hr = DB_E_CANTCANCEL;
		return hr;
	}
	STDMETHOD(GetDBSession)(
		_In_ REFIID riid,
		_Outptr_ IUnknown** ppSession)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("ICommandImpl::GetDBSession\n"));
		if( ppSession == NULL )
			return E_INVALIDARG;
		T* pT = (T*)this;
		ATLASSERT(pT->m_spUnkSite != NULL);
		return pT->m_spUnkSite->QueryInterface(riid, (void**) ppSession);
	}

ATLPREFAST_SUPPRESS(6387)
	template <class RowsetClass>
	HRESULT CreateRowset(
		_Inout_opt_ IUnknown* pUnkOuter,
		_In_ REFIID riid,
		_In_ DBPARAMS* pParams,
		_Out_ DBROWCOUNT* pcRowsAffected,
		_Outptr_opt_ IUnknown** ppRowset,
		_Inout_ RowsetClass*& pRowsetObj)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("ICommandImpl::CreateRowset\n"));

		HRESULT hr;
		T* pT = (T*)this;

		if (pT->m_strCommandText.Length() == 0)
		{
			ATLTRACE(atlTraceDBProvider, 2, _T("ICommandImpl::No command text specified.\n"));
			return DB_E_NOCOMMAND;
		}
		if (InlineIsEqualGUID(IID_NULL, riid))
		{
			ATLTRACE(atlTraceDBProvider, 2, _T("IID_NULL was specified in Execute, returning S_OK"));
			return S_OK;
		}
		if (ppRowset != NULL)
			*ppRowset = NULL;
		else
			return E_INVALIDARG;
		if ((pUnkOuter != NULL) && !InlineIsEqualUnknown(riid))
			return DB_E_NOAGGREGATION;
		CComPolyObject<RowsetClass>* pPolyObj;
		if (FAILED(hr = CComPolyObject<RowsetClass>::CreateInstance(pUnkOuter, &pPolyObj)))
			return hr;
		// Ref the created COM object and Auto release it on failure
		CComPtr<IUnknown> spUnk;
		hr = pPolyObj->QueryInterface(&spUnk);
		if (FAILED(hr))
		{
			delete pPolyObj; // must hand delete as it is not ref'd
			return hr;
		}
		// Get a pointer to the Rowset instance
		pRowsetObj = &(pPolyObj->m_contained);

		if (FAILED(hr = pRowsetObj->FInit(pT)))
			return hr;
		CComPtr<IUnknown> spOuterUnk;
		hr = pT->QueryInterface(__uuidof(IUnknown), (void**)&spOuterUnk);
		if (FAILED(hr))
		{
			return hr;
		}
		hr = pRowsetObj->SetSite(spOuterUnk);
		if (FAILED(hr))
		{
			return hr;
		}
		pRowsetObj->m_strCommandText = pT->m_strCommandText;

		// Check to make sure we set any 'post' properties based on the riid
		// requested.  Do this before calling Execute in case provider has
		// property specific processing.
		if (FAILED(pRowsetObj->OnInterfaceRequested(riid)))
			return hr;

		POSITION pos = pT->m_rgBindings.GetStartPosition();
		while( pos != NULL )
		{
			typename T::_BindingVector::CPair *pPair = pT->m_rgBindings.GetNext(pos);
			ATLENSURE_RETURN( pPair != NULL );
			typename T::_BindType* pBind = NULL;
			typename T::_BindType* pBindSrc = NULL;
			ATLTRY(pBind = _ATL_NEW typename T::_BindType);
			CAutoPtr<typename T::_BindType> amr(pBind);
			if (pBind == NULL)
			{
				ATLTRACE(atlTraceDBProvider, 2, _T("Failed to allocate memory for new Binding\n"));
				return E_OUTOFMEMORY;
			}
			pBindSrc = pPair->m_value;
			if (pBindSrc == NULL)
			{
				ATLTRACE(atlTraceDBProvider, 2, _T("The map appears to be corrupted, failing!!\n"));
				return E_FAIL;
			}
			_ATLTRY
			{
				pRowsetObj->m_rgBindings.SetAt(pPair->m_key, pBind);
			}
			_ATLCATCH( e )
			{
				_ATLDELETEEXCEPTION( e );
				ATLTRACE(atlTraceDBProvider, 2, _T("Failed to add hAccessor to Map\n"));
				return E_OUTOFMEMORY;
			}
			if (pBindSrc->cBindings)
			{
				pBind->pBindings = _ATL_NEW DBBINDING[pBindSrc->cBindings];
				if (pBind->pBindings == NULL)
				{
					ATLTRACE(atlTraceDBProvider, 2, _T("Failed to Allocate dbbinding Array\n"));
					// We added it, must now remove on failure
					pRowsetObj->m_rgBindings.RemoveKey(pPair->m_key);
					return E_OUTOFMEMORY;
				}
			}
			else
			{
				pBind->pBindings = NULL; // NULL Accessor
			}

			pBind->dwAccessorFlags = pBindSrc->dwAccessorFlags;
			pBind->cBindings = pBindSrc->cBindings;
			pBind->dwRef = 1;
			if (pBind->pBindings != NULL)
				Checked::memcpy_s(pBind->pBindings, pBind->cBindings*sizeof(DBBINDING), pBindSrc->pBindings, pBindSrc->cBindings*sizeof(DBBINDING));
			amr.Detach();
		}

		// Execute the command.
		// By default, we'll always return a rowset.  If you do not wish to
		// return a rowset, set this value to false in your Execute function.
		// If you return false, we will not return a rowset pointer, regardless
		// of whether the user requests one.
		m_bRowsetReturned = true;

		HRESULT hrExecute;
		if (FAILED(hrExecute = pRowsetObj->Execute(pParams, pcRowsAffected)))
			return hrExecute;
		if (InlineIsEqualGUID(riid, IID_NULL) || ppRowset == NULL)
		{
			if (ppRowset != NULL)
				*ppRowset = NULL;
			return hrExecute;
		}

		if (m_bRowsetReturned != false)
			hr = pPolyObj->QueryInterface(riid, (void**)ppRowset);
		else
			return hr;

		if (FAILED(hr))
			return hr;

		return hrExecute;
	}
ATLPREFAST_UNSUPPRESS()

	unsigned m_bIsExecuting:1;
	unsigned m_bCancelWhenExecuting:1;
	unsigned m_bCancel:1;
	unsigned m_bRowsetReturned:1;
};


template <class T>
class ATL_NO_VTABLE ICommandTextImpl :
	public ICommandImpl<T, ICommandText>
{
public:

	ICommandTextImpl()
	{
		m_guidDialect = DBGUID_DEFAULT;
		m_cRowsetsOpen = 0;
	}

	STDMETHOD(GetCommandText)(
		_Inout_opt_ GUID* pguidDialect,
		_Outptr_result_z_ LPOLESTR* ppwszCommand)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("ICommandTextImpl::GetCommandText\n"));

		T* pT = (T*)this;
		typename T::ObjectLock cab(pT);

		GUID guidOrig = IID_NULL;

		if (pguidDialect != NULL)
		{
			guidOrig = *pguidDialect;
			*pguidDialect = GUID_NULL;
		}
		if (ppwszCommand == NULL)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("ICommandTextImpl::GetCommandText Bad Command buffer\n"));
			return E_INVALIDARG;
		}
		if (m_strCommandText.m_str == NULL || *(m_strCommandText.m_str) == NULL)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("ICommandTextImpl::GetCommandText Bad Command buffer\n"));
			return DB_E_NOCOMMAND;
		}

		*ppwszCommand = AtlAllocTaskWideString(m_strCommandText.m_str);
		if( ppwszCommand != NULL )
		{
			HRESULT hrDialect = S_OK;
			if ( pguidDialect != NULL)
			{
				if(!InlineIsEqualGUID(guidOrig, m_guidDialect))
					hrDialect = DB_S_DIALECTIGNORED;
				*pguidDialect = m_guidDialect;
			}
			return hrDialect;
		}
		else
			return E_OUTOFMEMORY;
	}

	STDMETHOD(SetCommandText)(
		_In_ REFGUID rguidDialect,
		_In_z_ LPCOLESTR pwszCommand)
	{
		_ATLTRY
		{
			T* pT = (T*)this;
			typename T::ObjectLock cab(pT);

			ATLTRACE(atlTraceDBProvider, 2, _T("ICommandTextImpl::SetCommandText\n"));

			if (m_cRowsetsOpen > 0)
				return DB_E_OBJECTOPEN;

			if (InlineIsEqualGUID(rguidDialect, DBGUID_SQL))
			{
				// Get a pointer into the session
				CComPtr<IGetDataSource> spDataSource;
				CComPtr<IDBProperties> spProps;
				if( SUCCEEDED( pT->GetSite( __uuidof(IGetDataSource), (void**)&spDataSource ) ) &&
					SUCCEEDED( spDataSource->GetDataSource( __uuidof(IDBProperties), (IUnknown**)&spProps ) ) )
				{
					CDBPropIDSet set(DBPROPSET_DATASOURCEINFO);
					if(!set.AddPropertyID(DBPROP_SQLSUPPORT))
					{
						return E_FAIL;
					}
					DBPROPSET* pPropSet = NULL;
					ULONG ulPropSet = 0;
					HRESULT hr = spProps->GetProperties(1, &set, &ulPropSet, &pPropSet);
					if( SUCCEEDED(hr) && pPropSet != NULL )
					{
						CComVariant var = pPropSet->rgProperties[0].vValue;
						CoTaskMemFree(pPropSet->rgProperties);
						CoTaskMemFree(pPropSet);

						if( var.lVal == DBPROPVAL_SQL_NONE )
							return DB_E_BADCONVERTFLAG;
					}
					else
						return DB_E_BADCONVERTFLAG;
				}
				return DB_E_BADCONVERTFLAG;
			}
			if (InlineIsEqualGUID(rguidDialect, GUID_NULL))
				return DB_E_DIALECTNOTSUPPORTED;
			m_guidDialect = rguidDialect;
			m_strCommandText = pwszCommand;
		}
		_ATLCATCHALL()
		{
			return E_FAIL;
		}
		return S_OK;
	}

	LONG m_cRowsetsOpen;
	CComBSTR m_strCommandText;
	GUID m_guidDialect;
};

// ISessionPropertiesImpl
template <class T, class PropClass = T>
class ATL_NO_VTABLE ISessionPropertiesImpl :
	public ISessionProperties,
	public CUtlProps<PropClass>
{
public:
	typedef PropClass _PropClass;

	STDMETHOD(GetProperties)(
		_In_ ULONG cPropertyIDSets,
		_In_reads_(cPropertyIDSets) const DBPROPIDSET rgPropertyIDSets[],
		_Out_ ULONG *pcPropertySets,
		_Outptr_result_buffer_maybenull_(*pcPropertySets) DBPROPSET **prgPropertySets)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("ISessionPropertiesImpl::GetProperties\n"));
		T* pT;
		pT = static_cast<T*>(this);

		// You can't retrieve PROPERTIESINERROR here (it would be processed
		// like any other property set.  Therefore, turn checking off
		this->m_dwFlags &= ~ARGCHK_PROPERTIESINERROR;
		HRESULT hr = this->GetPropertiesArgChk(cPropertyIDSets, rgPropertyIDSets,
			pcPropertySets, prgPropertySets);

		if(SUCCEEDED(hr))
		{
			// Scan property sets to allow user defined properties
			ULONG ulPropSets = 0;
			ULONG ulPropElems = 0;
			ULONG ulPropInits = 0;
			UPROPSET* pSetA = NULL;
			UPROPSET* pSetTemp = NULL;
			ULONG l=0;
			ULONG cSets = (ULONG)(ULONG_PTR)pT->_GetPropSet(NULL, &ulPropElems);

			CTempBuffer<UPROPSET> tmpBuffer;
			pSetA = tmpBuffer.Allocate(cSets);
			if (pSetA == NULL)
				return E_OUTOFMEMORY;  // We shouldn't get this but...
			pSetTemp = pT->_GetPropSet(&ulPropSets, &ulPropElems, pSetA);

			typedef const GUID* PCGUID;

			for(l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ulPropInits++;
			}

			CTempBuffer<PCGUID> tmpBuffer2;
			PCGUID* ppGuid = tmpBuffer2.Allocate(ulPropInits);
			if (ppGuid == NULL)
				return E_OUTOFMEMORY;
			ulPropInits = 0;
			for (l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ppGuid[ulPropInits++] = pSetTemp[l].pPropSet;
			}

			hr = CUtlProps<PropClass>::GetProperties(cPropertyIDSets,
					rgPropertyIDSets, pcPropertySets, prgPropertySets,
					ulPropInits, ppGuid);
		}
		return hr;

	}

	STDMETHOD(SetProperties)(
		_In_ ULONG cPropertySets,
		_In_reads_(cPropertySets) DBPROPSET rgPropertySets[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("ISessionPropertiesImpl::SetProperties"));
		T* pT;
		pT = static_cast<T*>(this);
		HRESULT hr = CUtlPropsBase::SetPropertiesArgChk(cPropertySets, rgPropertySets);

		if(SUCCEEDED(hr))
		{
			// Scan property sets to allow user defined properties
			ULONG ulPropSets = 0;
			ULONG ulPropElems = 0;
			ULONG ulPropInits = 0;
			UPROPSET* pSetA = NULL;
			UPROPSET* pSetTemp = NULL;
			ULONG l=0;
			ULONG cSets = (ULONG)(ULONG_PTR)pT->_GetPropSet(NULL, &ulPropElems);

			CTempBuffer<UPROPSET> tmpBuffer;
			pSetA = tmpBuffer.Allocate(cSets);
			if (pSetA == NULL)
				return E_OUTOFMEMORY;  // We shouldn't get this but...
			pSetTemp = pT->_GetPropSet(&ulPropSets, &ulPropElems, pSetA);

			typedef const GUID* PCGUID;

			for(l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ulPropInits++;
			}

			CTempBuffer<PCGUID> tmpBuffer2;
			PCGUID* ppGuid = tmpBuffer2.Allocate(ulPropInits);
			if (ppGuid == NULL)
				return E_OUTOFMEMORY;
			ulPropInits = 0;
			for (l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ppGuid[ulPropInits++] = pSetTemp[l].pPropSet;
			}

			hr = CUtlProps<PropClass>::SetProperties(0, cPropertySets, rgPropertySets,
					ulPropInits, ppGuid);
		}
		return hr;
	}
};

// Implementation Class
template <class BindType>
class ATL_NO_VTABLE IAccessorImplBase :
	public IAccessor
{
public:

	STDMETHOD(CreateAccessor)(
		_In_ DBACCESSORFLAGS dwAccessorFlags,
		_In_ DBCOUNTITEM cBindings,
		_In_reads_(cBindings) const DBBINDING rgBindings[],
		_In_ DBLENGTH /*cbRowSize*/,
		_Out_ HACCESSOR *phAccessor,
		_Out_writes_(cBindings) DBBINDSTATUS rgStatus[])
	{
		if (!(dwAccessorFlags & DBACCESSOR_PARAMETERDATA) && !(dwAccessorFlags & DBACCESSOR_ROWDATA))
			return DB_E_BADACCESSORFLAGS;
		if (dwAccessorFlags == DBACCESSOR_INVALID)
			return DB_E_BADACCESSORFLAGS;
		if (dwAccessorFlags > 0x000F)
			return DB_E_BADACCESSORFLAGS;
		CAutoPtr<BindType> pBind;
ATLPREFAST_SUPPRESS(6001)
		ATLTRY(pBind . Attach ( new BindType ) )
ATLPREFAST_UNSUPPRESS()
		if (pBind == NULL)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("Failed to allocate ATL Binding struct\n"));
			return E_OUTOFMEMORY;
		}
		if (cBindings)
		{
			pBind->pBindings = _ATL_NEW DBBINDING[cBindings];
			if (pBind->pBindings == NULL)
			{
				return E_OUTOFMEMORY;
			}
		}
		else
			pBind->pBindings = NULL; // NULL Accessor

		pBind->dwAccessorFlags = dwAccessorFlags;
		pBind->cBindings = cBindings;
		pBind->dwRef = 1;
		if (pBind->pBindings)
			Checked::memcpy_s(pBind->pBindings, pBind->cBindings*sizeof(DBBINDING), rgBindings, cBindings*sizeof(DBBINDING));
		DBBINDSTATUS status = DBBINDSTATUS_OK;
		memset (rgStatus, status, sizeof(DBBINDSTATUS)*cBindings);
		*phAccessor = (ULONG_PTR)pBind.Detach();
		return S_OK;
	}
	BOOL HasFlag(
		_In_ DBTYPE dbToCheck,
		_In_ DBTYPE dbCombo)
	{
		return ( (dbToCheck & dbCombo) == dbCombo );
	}
	HRESULT ValidateBindings(
		_In_ DBCOUNTITEM cBindings,
		_In_reads_(cBindings) const DBBINDING rgBindings[],
		_Out_writes_(cBindings) DBBINDSTATUS rgStatus[],
		_In_ bool bHasBookmarks)
	{
		HRESULT hr = S_OK;

		for (ULONG iBinding = 0; iBinding < cBindings; iBinding++)
		{
			const DBBINDING& rBindCur = rgBindings[iBinding];
			if (rBindCur.iOrdinal == 0)
			{
				if (!m_bIsCommand && !bHasBookmarks)
				{
					hr = DB_E_ERRORSOCCURRED;
					rgStatus[iBinding] = DBBINDSTATUS_BADORDINAL;
					continue;
				}
			}
			if (rBindCur.dwPart == 0) // nothing to bind to
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
				continue;
			}
			if (HasFlag(rBindCur.wType, (DBTYPE_BYREF | DBTYPE_ARRAY)))
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
				continue;
			}
			if (HasFlag(rBindCur.wType, (DBTYPE_BYREF | DBTYPE_VECTOR)))
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
				continue;
			}
			if (HasFlag(rBindCur.wType, (DBTYPE_VECTOR | DBTYPE_ARRAY)))
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
				continue;
			}
			if (rBindCur.wType == DBTYPE_NULL || rBindCur.wType == DBTYPE_EMPTY)
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
				continue;
			}
			if (HasFlag(rBindCur.wType, DBTYPE_RESERVED))
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
				continue;
			}
			// DBTYPE_BYREF must be accompanied by other valid type
			if (rBindCur.wType == (DBTYPE_BYREF | DBTYPE_EMPTY)
				|| rBindCur.wType == (DBTYPE_BYREF | DBTYPE_NULL)
				|| rBindCur.wType == DBTYPE_BYREF)
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
				continue;
			}

			// If someone attempts to set DBBINDFLAG_HTML on a non-string
			// column, generate a BADBINDINFO error.
			if (rBindCur.wType != DBTYPE_STR && rBindCur.wType != DBTYPE_WSTR &&
				rBindCur.dwFlags & DBBINDFLAG_HTML)
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
				continue;
			}

			// the dwFlags parameter must be 0 or DBBINDFLAG_HTML,
			// all other values will return an error.
			if (rBindCur.dwFlags != 0 && rBindCur.dwFlags != DBBINDFLAG_HTML)
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
				continue;
			}

			if (rBindCur.dwMemOwner == DBMEMOWNER_PROVIDEROWNED)
			{
				BOOL bIsPointerType = HasFlag(rBindCur.wType, DBTYPE_BYREF) ||
									  HasFlag(rBindCur.wType, DBTYPE_VECTOR) ||
									  HasFlag(rBindCur.wType, DBTYPE_ARRAY) ||
									  HasFlag( (DBTYPE) (~(DBTYPE_BYREF) & rBindCur.wType), DBTYPE_BSTR);
				if (!bIsPointerType)
				{
					hr = DB_E_ERRORSOCCURRED;
					rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
					continue;
				}
			}
			else if (rBindCur.dwMemOwner != DBMEMOWNER_CLIENTOWNED)
			{
				// the dwMemOwner flag is supposed to be ignored for reference accessors
				// (see the description of DBBINDING structure in MSDN), otherwise dwMemOwner
				// should have a value of DBMEMOWNER_CLIENTOWNED or DBMEMOWNER_PROVIDEROWNED
				if (!HasFlag(rBindCur.wType, DBTYPE_BYREF))
				{
					hr = DB_E_ERRORSOCCURRED;
					rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
					continue;
				}
			}

		}
		return hr;
	}

	unsigned  m_bIsCommand:1;
	unsigned  m_bHasParamaters:1;
	unsigned  m_bIsChangeable:1;
};

// IAccessorImpl
template <class T, class BindType = ATLBINDINGS,
			class BindingVector = CAtlMap < HACCESSOR, BindType* > >
class ATL_NO_VTABLE IAccessorImpl :
	public IAccessorImplBase<BindType>
{
public:
	typedef BindType _BindType;
	typedef BindingVector _BindingVector;
	IAccessorImpl()
	{
		this->m_bIsCommand = FALSE;
		this->m_bHasParamaters = FALSE;
		this->m_bIsChangeable = FALSE;
	}
	OUT_OF_LINE HRESULT InternalFinalConstruct(_In_opt_ IUnknown* /*pUnkThis*/)
	{
		CComPtr<ICommand> spCommand;
		CComPtr<ICommandWithParameters> spCommandWithParameters;
		T* pT = (T*)this;

		pT->_InternalQueryInterface(__uuidof(ICommand),(void **) &spCommand);

		if (spCommand !=NULL)  // It's a command
		{
			this->m_bIsCommand = TRUE;
			pT->_InternalQueryInterface(__uuidof(ICommandWithParameters), (void **) &spCommandWithParameters);
			this->m_bHasParamaters =  spCommandWithParameters != NULL;

		}
		return S_OK;
	}
	HRESULT FinalConstruct()
	{
		T* pT = (T*)this;
		return InternalFinalConstruct(pT->GetUnknown());
	}
	void FinalRelease()
	{
#ifdef _DEBUG
		if (m_rgBindings.GetCount())
			ATLTRACE(atlTraceDBProvider, 0, _T("IAccessorImpl::~IAccessorImpl Bindings still in vector, removing\n"));
#endif //_DEBUG
		while (m_rgBindings.GetCount())
			ReleaseAccessor((HACCESSOR)m_rgBindings.GetKeyAt(m_rgBindings.GetStartPosition()), NULL);
	}
	STDMETHOD(AddRefAccessor)(
		_In_ HACCESSOR hAccessor,
		_Out_opt_ DBREFCOUNT *pcRefCount)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IAccessorImpl::AddRefAccessor\n"));
		if (hAccessor == NULL)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("AddRefAccessor : Bad hAccessor\n"));
			return DB_E_BADACCESSORHANDLE;
		}
		BindType* pBind;
		if( ! m_rgBindings.Lookup(hAccessor, pBind ) )
			return DB_E_BADACCESSORHANDLE;

		ATLENSURE_RETURN( pBind );
		ULONG cRefCount = T::_ThreadModel::Increment((LONG*)&pBind->dwRef);

		if (pcRefCount != NULL)
			*pcRefCount = cRefCount;

		return S_OK;
	}
	OUT_OF_LINE ATLCOLUMNINFO* ValidateHelper(
		_Out_ DBORDINAL* pcCols,
		_Inout_ CComPtr<IDataConvert>& rspConvert)
	{
		T* pT = (T*)this;
		rspConvert = pT->m_spConvert;
		return T::GetColumnInfo(pT, pcCols);
	}
	OUT_OF_LINE HRESULT ValidateBindingsFromMetaData(
		_In_ DBCOUNTITEM cBindings,
		_In_reads_(cBindings) const DBBINDING rgBindings[],
		_Out_writes_(cBindings) DBBINDSTATUS rgStatus[],
		_In_ bool bHasBookmarks)
	{
		HRESULT hr = S_OK;
		DBORDINAL cCols;
		CComPtr<IDataConvert> spConvert;
		ATLCOLUMNINFO* pColInfo = ValidateHelper(&cCols, spConvert);
		ATLENSURE_RETURN(pColInfo != NULL);
		for (DBCOUNTITEM iBinding = 0; iBinding < cBindings; iBinding++)
		{
			const DBBINDING& rBindCur = rgBindings[iBinding];
			// Bookmarks start with ordinal 0, non-bookmarks start w/ ordinal 1
			DBORDINAL iOrdAdjusted = bHasBookmarks ? rBindCur.iOrdinal : rBindCur.iOrdinal - 1;

			if (rBindCur.iOrdinal > cCols)
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_BADORDINAL;
				continue;
			}

			// If a binding specifies provider owned memory, and specifies type
			// X | BYREF, and the provider's copy is not X or X | BYREF, return
			// DBBINDSTATUS_BADBINDINFO

			if (rBindCur.dwMemOwner == DBMEMOWNER_PROVIDEROWNED)
			{
				if ((rBindCur.wType & DBTYPE_BYREF) != 0 &&
					((rBindCur.wType & (~DBTYPE_BYREF)) !=
						(pColInfo[iOrdAdjusted].wType & (~DBTYPE_BYREF))))
				{
					hr = DB_E_ERRORSOCCURRED;
					rgStatus[iBinding] = DBBINDSTATUS_BADBINDINFO;
					continue;
				}
			}

			ATLASSERT(spConvert != NULL);
			HRESULT hrConvert = spConvert->CanConvert(pColInfo[iOrdAdjusted].wType, rBindCur.wType);
			if (FAILED(hrConvert) || hrConvert == S_FALSE)
			{
				hr = DB_E_ERRORSOCCURRED;
				rgStatus[iBinding] = DBBINDSTATUS_UNSUPPORTEDCONVERSION;
				continue;
			}
		}
		return hr;
	}
	STDMETHOD(CreateAccessor)(
		_In_ DBACCESSORFLAGS dwAccessorFlags,
		_In_ DBCOUNTITEM cBindings,
		_In_reads_(cBindings) const DBBINDING rgBindings[],
		_In_ DBLENGTH cbRowSize,
		_Out_ HACCESSOR *phAccessor,
		_Out_writes_(cBindings) DBBINDSTATUS rgStatus[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IAccessorImpl::CreateAccessor\n"));
		T* pT = (T*)this;
		typename T::ObjectLock cab(pT);

		if (!phAccessor)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("IAccessorImpl::CreateAccessor : Invalid NULL Parameter for HACCESSOR*\n"));
			return E_INVALIDARG;
		}
		*phAccessor = NULL;
		if (cBindings != 0 && rgBindings == NULL)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("IAccessorImpl::CreateAccessor  : Bad Binding array\n"));
			return E_INVALIDARG;
		}
		if (dwAccessorFlags & DBACCESSOR_PASSBYREF)
		{
			CComVariant varByRef;
			HRESULT hr = pT->GetPropValue(&DBPROPSET_ROWSET,
				DBPROP_BYREFACCESSORS, &varByRef);
			if (FAILED(hr) || varByRef.boolVal == ATL_VARIANT_FALSE)
				return DB_E_BYREFACCESSORNOTSUPPORTED;
		}
		if (!this->m_bHasParamaters)
		{
			if (dwAccessorFlags & DBACCESSOR_PARAMETERDATA)
				return DB_E_BADACCESSORFLAGS;
		}

		// since our accessor does not provide any further restrictions or optimizations based
		// on the DBACCESSOR_OPTIMIZED flag, the flag will be ignored.  In particular we will
		// not be returning this flag in the call to IAccessor::GetBindings.  This way we will
		// be compliant with the OLEDB specifications and we will not have to prevent the
		// client from creating additional accessors after the first row is fetched.
		DBACCESSORFLAGS dwMask = DBACCESSOR_OPTIMIZED;
		dwAccessorFlags &= ~dwMask;

		CComVariant varUpdate;
		HRESULT hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_UPDATABILITY, &varUpdate);
		this->m_bIsChangeable = (SUCCEEDED(hr) && (varUpdate.iVal & DBPROPVAL_UP_INSERT));

		if (this->m_bIsCommand || !this->m_bIsChangeable)
		{
			if (cBindings == 0) // No NULL Accessors on the command
				return DB_E_NULLACCESSORNOTSUPPORTED;
		}

		CTempBuffer<DBBINDSTATUS> tmpBuffer;
		if (rgStatus == NULL && cBindings) // Create a fake status array
			rgStatus = tmpBuffer.Allocate(cBindings);

		// Validate the Binding passed
		bool bHasBookmarks = false;
		CComVariant varBookmarks;
		HRESULT hrLocal = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_BOOKMARKS, &varBookmarks);
		bHasBookmarks = (hrLocal == S_OK &&  varBookmarks.boolVal != ATL_VARIANT_FALSE);

		hr = this->ValidateBindings(cBindings, rgBindings, rgStatus, bHasBookmarks);

		if (FAILED(hr))
			return hr;
		if (!this->m_bIsCommand)
		{
			hr = ValidateBindingsFromMetaData(cBindings, rgBindings, rgStatus,
					bHasBookmarks);
			if (FAILED(hr))
				return hr;
		}
		hr = IAccessorImplBase<BindType>::CreateAccessor(dwAccessorFlags, cBindings,
			rgBindings, cbRowSize, phAccessor,rgStatus);
		if (SUCCEEDED(hr))
		{
			ATLASSERT(*phAccessor != NULL);
			BindType* pBind = (BindType*)*phAccessor;
			ATLENSURE_RETURN(pBind);
			_ATLTRY
			{
				m_rgBindings.SetAt((HACCESSOR)pBind, pBind);
				hr = S_OK;
			}
			_ATLCATCH( e )
			{
				_ATLDELETEEXCEPTION( e );
				hr = E_OUTOFMEMORY;
			}
		}
		return hr;

	}

	STDMETHOD(GetBindings)(
		_In_ HACCESSOR hAccessor,
		_Out_ DBACCESSORFLAGS *pdwAccessorFlags,
		_Out_ DBCOUNTITEM *pcBindings,
		_Outptr_opt_result_buffer_maybenull_(*pcBindings) DBBINDING **prgBindings)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IAccessorImpl::GetBindings\n"));

		// Zero output parameters in case of failure
		if (pdwAccessorFlags != NULL)
			*pdwAccessorFlags = NULL;

		if (pcBindings != NULL)
			*pcBindings = NULL;

		if (prgBindings != NULL)
			*prgBindings = NULL;

		// Check if any of the out params are NULL pointers
		if ((pdwAccessorFlags && pcBindings && prgBindings) == NULL)
			return E_INVALIDARG;

		BindType* pBind;
		bool bFound = m_rgBindings.Lookup((INT_PTR)hAccessor, pBind);
		HRESULT hr = DB_E_BADACCESSORHANDLE;
		if (bFound && pBind != NULL)
		{
			*pdwAccessorFlags = pBind->dwAccessorFlags;
			*pcBindings = pBind->cBindings;
			// Get NULL for NULL Accessor
			*prgBindings = (pBind->cBindings) ? (DBBINDING*)::ATL::AtlCoTaskMemCAlloc(*pcBindings, static_cast<ULONG>(sizeof(DBBINDING))) : NULL;
			if (*prgBindings == NULL && pBind->cBindings) // No Error if NULL Accessor
				return E_OUTOFMEMORY;
			Checked::memcpy_s(*prgBindings, *pcBindings * sizeof(DBBINDING), pBind->pBindings, *pcBindings * sizeof(DBBINDING));
			hr = S_OK;
		}
		return hr;
	}

	STDMETHOD(ReleaseAccessor)(
		_In_ HACCESSOR hAccessor,
		_Out_opt_ DBREFCOUNT *pcRefCount)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IAccessorImpl::ReleaseAccessor\n"));
		typename T::ObjectLock cab((T*)this);
		BindType* pBind;
		bool bFound = m_rgBindings.Lookup((INT_PTR)hAccessor, pBind);
		if (!bFound || pBind == NULL)
			return DB_E_BADACCESSORHANDLE;
		DBREFCOUNT cRefCount = T::_ThreadModel::Decrement((LONG*)&pBind->dwRef);
		if (pcRefCount != NULL)
			*pcRefCount = cRefCount;
		if (cRefCount == 0)
		{
			delete [] pBind->pBindings;
			delete pBind;
			return m_rgBindings.RemoveKey(hAccessor) ? S_OK : DB_E_BADACCESSORHANDLE;
		}
		return S_OK;
	}

	BindingVector m_rgBindings;
};

#define BEGIN_PROVIDER_COLUMN_MAP(theClass) \
	typedef theClass _Class; \
	template <class T> \
	_Ret_writes_(*pcCols) static ATL::ATLCOLUMNINFO* GetColumnInfo( \
		_In_opt_ _Post_readable_byte_size_(sizeof(T)) T* pv, \
		_Out_ DBORDINAL* pcCols) \
	{ \
	UNREFERENCED_PARAMETER(pv); \
	static ATL::ATLCOLUMNINFO _rgColumns [] = \
	{

#define SIZEOF_MEMBER(memberOf, member) \
	sizeof(((memberOf*)0)->member)
#define EXPANDGUID(guid) \
	{ guid.Data1, guid.Data2, guid.Data3, \
	{ guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7] } }

#define PROVIDER_COLUMN_ENTRY_GN(name, ordinal, flags, colSize, dbtype, precision, scale, guid) \
{ (LPOLESTR)name, (ITypeInfo*)NULL, (DBORDINAL)ordinal, (DBCOLUMNFLAGS)flags, (DBLENGTH)colSize, (DBTYPE)dbtype, (BYTE)precision, (BYTE)scale, { EXPANDGUID(guid), (DWORD)0, (LPOLESTR) name}, 0},

#define PROVIDER_COLUMN_ENTRY_EX(name, typeinfo, ordinal, flags, colSize, dbtype, precision, scale, guid, member) \
	{ \
		(LPOLESTR)OLESTR(name), \
		typeinfo, \
		(DBORDINAL)ordinal, \
		flags, \
		colSize, \
		dbtype, \
		(BYTE)precision, \
		(BYTE)scale, \
		{ \
			EXPANDGUID(guid), \
			(DWORD)DBKIND_NAME, \
			(LPOLESTR)OLESTR(name) \
		}, \
		offsetof(_Class, member) \
	},

#define PROVIDER_COLUMN_ENTRY(name, ordinal, member) \
	PROVIDER_COLUMN_ENTRY_EX(name, NULL, ordinal, DBCOLUMNFLAGS_ISFIXEDLENGTH, (DBLENGTH)SIZEOF_MEMBER(_Class, member), ATL::_GetOleDBType(((_Class*)0)->member), 0, 0, GUID_NULL, member)

#define PROVIDER_COLUMN_ENTRY_PS(name, ordinal, precision, scale, member) \
	PROVIDER_COLUMN_ENTRY_EX(name, NULL, ordinal, DBCOLUMNFLAGS_ISFIXEDLENGTH, (DBLENGTH)SIZEOF_MEMBER(_Class, member), ATL::_GetOleDBType(((_Class*)0)->member), precision, scale, GUID_NULL, member)

#define PROVIDER_COLUMN_ENTRY_LENGTH(name, ordinal, size, member) \
	PROVIDER_COLUMN_ENTRY_EX(name, NULL, ordinal, DBCOLUMNFLAGS_ISFIXEDLENGTH, size, ATL::_GetOleDBType(((_Class*)0)->member), 0, 0, GUID_NULL, member)

#define PROVIDER_COLUMN_ENTRY_TYPE_LENGTH(name, ordinal, type, size, member) \
	PROVIDER_COLUMN_ENTRY_EX(name, NULL, ordinal, DBCOLUMNFLAGS_ISFIXEDLENGTH, size, type, 0, 0, GUID_NULL, member)

#define PROVIDER_COLUMN_ENTRY_TYPE(name, ordinal, type, member) \
	PROVIDER_COLUMN_ENTRY_EX(name, NULL, ordinal, DBCOLUMNFLAGS_ISFIXEDLENGTH, (DBLENGTH)SIZEOF_MEMBER(_Class, member), type, 0, 0, GUID_NULL, member)

#define PROVIDER_COLUMN_ENTRY_TYPE_PS(name, ordinal, type, precision, scale, member) \
	PROVIDER_COLUMN_ENTRY_EX(name, NULL, ordinal, DBCOLUMNFLAGS_ISFIXEDLENGTH, (DBLENGTH)SIZEOF_MEMBER(_Class, member), type, precision, scale, GUID_NULL, member)

#define PROVIDER_COLUMN_ENTRY_FIXED(name, ordinal, type, member) \
	PROVIDER_COLUMN_ENTRY_EX(name, NULL, ordinal, DBCOLUMNFLAGS_ISFIXEDLENGTH, (DBLENGTH)SIZEOF_MEMBER(_Class, member), type, 0, 0, GUID_NULL, member)

#define PROVIDER_COLUMN_ENTRY_STR(name, ordinal, member) \
	PROVIDER_COLUMN_ENTRY_EX(name, NULL, ordinal, 0, (DBLENGTH)SIZEOF_MEMBER(_Class, member), DBTYPE_STR, 255, 255, GUID_NULL, member)

#define PROVIDER_COLUMN_ENTRY_WSTR(name, ordinal, member) \
	PROVIDER_COLUMN_ENTRY_EX(name, NULL, ordinal, 0, (DBLENGTH)SIZEOF_MEMBER(_Class, member), DBTYPE_WSTR, 255, 255, GUID_NULL, member)

#define END_PROVIDER_COLUMN_MAP() \
}; *pcCols = sizeof(_rgColumns)/sizeof(ATL::ATLCOLUMNINFO); return _rgColumns;}

class CDynColumnInfo:
	public CAtlArray<ATLCOLUMNINFO>
{
public:
	HRESULT Add(
		_In_z_ LPOLESTR szName,
		_In_ DBORDINAL ordinal,
		_In_ DBLENGTH colSize,
		_In_ DBTYPE type,
		_In_ BYTE precision,
		_In_ BYTE scale,
		_In_ DBBYTEOFFSET offset,
		_In_ DWORD flags = DBCOLUMNFLAGS_ISFIXEDLENGTH)
	{
		ATLCOLUMNINFO col;
		col.pwszName = szName;
		col.pTypeInfo = NULL;
		col.iOrdinal = ordinal;
		col.dwFlags = flags;
		col.ulColumnSize = colSize;
		col.wType = type;
		col.bPrecision = precision;
		col.bScale = scale;
		col.cbOffset = offset;
		memset(&(col.columnid), 0, sizeof(DBID));
		col.columnid.uName.pwszName = (LPOLESTR)szName;
		_ATLTRY
		{
			CAtlArray<ATLCOLUMNINFO>::Add(col);
		}
		_ATLCATCH( e )
		{
			_ATLDELETEEXCEPTION( e );
			return E_OUTOFMEMORY;
		}
		return S_OK;
	}

	HRESULT AddString(
		_In_z_ LPOLESTR szName,
		_In_ DBORDINAL ordinal,
		_In_ DBLENGTH colSize,
		_In_ DBBYTEOFFSET offset)
	{
		return Add(szName,ordinal, colSize, DBTYPE_STR, 0xFF, 0xFF, offset);
	}
};

// Implementation Class
class CSimpleRow
{
public:
	typedef DBCOUNTITEM KeyType;

	CSimpleRow(_In_ DBCOUNTITEM iRowsetCur)
	{
		m_dwRef = 0;
		m_pData = NULL;
		m_iRowset = iRowsetCur;
		m_iOriginalRowset = iRowsetCur; // used for stronger identity tests
		m_status = 0;
	}
	~CSimpleRow()
	{
		m_pData = NULL;	// Data will be freed seperately
	}
	DWORD AddRefRow()
	{
		return CComObjectThreadModel::Increment((LPLONG)&m_dwRef);
	}
	DWORD ReleaseRow()
	{
		return CComObjectThreadModel::Decrement((LPLONG)&m_dwRef);
	}

	HRESULT Compare(_In_ CSimpleRow* pRow)
	{
		ATLENSURE_RETURN(pRow != NULL);
		return (m_iRowset == pRow->m_iRowset) ? S_OK : S_FALSE;
	}

	DWORD	m_dwRef;
	void*	m_pData;			// NEW:  For IRowsetChange & IRowsetUpdate
	DBPENDINGSTATUS m_status;	// NEW:  For IRowsetUpdate
	KeyType m_iOriginalRowset;	// NEW:  For IRowsetChange & IRowsetUpdate
	KeyType m_iRowset;
};

template <class T>
inline void SendColumnSetFailureNotification(
	_Inout_updates_(1) T* pT,
	_In_ HROW hNotifyRow,
	_In_ typename T::_BindType* pBinding,
	_In_ CAtlArray<DBORDINAL>& rgColumns)
{
	pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
		rgColumns.GetData(), DBREASON_COLUMN_SET,
		DBEVENTPHASE_FAILEDTODO, TRUE);
}

template <class T, class RowClass>
inline void SendRowsFirstChangeFailureNotification(
	_Inout_updates_(1) T* pT,
	_In_ RowClass* pRow,
	_In_ HROW *phNotifyRow,
	_In_ bool bDeferred)
{
	ATLENSURE(pRow);
	if (bDeferred)
	{
		// Determine if we are really in a first row change
		if (pRow->m_status != DBPENDINGSTATUS_CHANGED &&
			pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
		{
			pT->Fire_OnRowChange(pT, 1, phNotifyRow,
				DBREASON_ROW_FIRSTCHANGE, DBEVENTPHASE_FAILEDTODO, TRUE);
		}
	}
}

template <class T, class RowClass, class MapClass>
HRESULT TransferData(
	_Inout_updates_(1) T* pT,
	_In_ bool bReading,
	_In_opt_ void* pData,
	_In_ RowClass* pRow,
	_In_opt_ MapClass* /*pMap*/,
	_In_ HACCESSOR hAccessor)
{
	ATLTRACE(atlTraceDBProvider, 2, _T("TransferData\n"));
	bool bFailed = false;
	bool bSucceeded = false;
	HRESULT hr = S_OK;

	HROW hNotifyRow = NULL;
	CAtlArray<DBORDINAL> rgColumns;
	_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
	{
		//HROW hNotifyRow = pT->m_rgRowHandles.ReverseLookup(pRow);
		{
			POSITION pos = pT->m_rgRowHandles.GetStartPosition();
			while( pos != NULL )
			{
				typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext( pos );
				ATLASSUME( pPair != NULL );
				if( pPair->m_value == pRow )
				{
					hNotifyRow = pPair->m_key;
					break;
				}
			}
		}
	}

	bool bDeferred;
	_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
	{
		// We need to send the DBREASON_ROW_FIRSTCHANGE notification's
		// SYNCHAFTER phase in this function.  IFF. we're deferred and
		// we have a newly changed row.

		CComVariant varDeferred;
		hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_IRowsetUpdate,
							&varDeferred);
		(FAILED(hr) || varDeferred.boolVal == ATL_VARIANT_FALSE) ? bDeferred = false : bDeferred = true;
	}

	// Check for a deleted row
	if( pRow->m_iRowset >= pT->m_rgRowData.GetCount() )
	{
		_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
		{
			if( !bReading )
			{
				SendRowsFirstChangeFailureNotification( pT, pRow, &hNotifyRow, bDeferred );
			}
		}
		return DB_E_DELETEDROW;
	}

	// NOTE: This was checking against DBPENDINGSTATUS_DELETED.  Instead, it
	// should check for DBPENDINGSTATUS_INVALIDROW (means a forced deleted
	// row).

	if (pRow->m_status == DBPENDINGSTATUS_INVALIDROW)
	{
		_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
		{
			if( !bReading )
			{
				SendRowsFirstChangeFailureNotification( pT, pRow, &hNotifyRow, bDeferred );
			}
		}
		return DB_E_DELETEDROW;
	}

	typename T::_BindType* pBinding;
	bool bFound = pT->m_rgBindings.Lookup((INT_PTR)hAccessor, pBinding);
	if (!bFound || pBinding == NULL)
	{
		_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
		{
			if( !bReading )
			{
				SendRowsFirstChangeFailureNotification( pT, pRow, &hNotifyRow, bDeferred );
			}
		}
		return DB_E_BADACCESSORHANDLE;
	}

	if (pData == NULL && pBinding->cBindings != 0)
	{
		_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
		{
			if( !bReading )
			{
				SendRowsFirstChangeFailureNotification( pT, pRow, &hNotifyRow, bDeferred );
			}
		}
		return E_INVALIDARG;
	}

	void* pDstData;
	void* pSrcData;
	if (bReading)
	{
		pDstData = pData;
		pSrcData = (void*)&(pT->m_rgRowData[(LONG)pRow->m_iRowset]);
	}
	else
	{
		pSrcData = pData;
		pDstData = (void*)&(pT->m_rgRowData[(LONG)pRow->m_iRowset]);
	}

	if (!bReading)
	{
		HRESULT hrNotify;
		// Send the OKTODO notification
		_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
		{
			if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
				pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
			{
				for (DBORDINAL l=0; l<pBinding->cBindings; l++)
				{
					_ATLTRY
					{
						rgColumns.Add(pBinding->pBindings[l].iOrdinal);
					}
					_ATLCATCH( e )
					{
						_ATLDELETEEXCEPTION( e );
						return E_FAIL;
					}
				}

				hrNotify = pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
					rgColumns.GetData(), DBREASON_COLUMN_SET,
					DBEVENTPHASE_OKTODO, FALSE);
				if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
				{
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						if (bDeferred)
							pT->Fire_OnRowChange(pT, 1, &hNotifyRow,
								DBREASON_ROW_FIRSTCHANGE, DBEVENTPHASE_FAILEDTODO, TRUE);
						return DB_E_CANCELED;
					}
				}

				hrNotify = pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
					rgColumns.GetData(), DBREASON_COLUMN_SET, DBEVENTPHASE_ABOUTTODO,
					FALSE);
				if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
				{
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						if (bDeferred)
							pT->Fire_OnRowChange(pT, 1, &hNotifyRow,
								DBREASON_ROW_FIRSTCHANGE, DBEVENTPHASE_FAILEDTODO, TRUE);
						return DB_E_CANCELED;
					}
				}

				hrNotify = pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
					rgColumns.GetData(), DBREASON_COLUMN_SET,
					DBEVENTPHASE_SYNCHAFTER, FALSE);
				if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
				{
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						if (bDeferred)
							pT->Fire_OnRowChange(pT, 1, &hNotifyRow,
								DBREASON_ROW_FIRSTCHANGE, DBEVENTPHASE_FAILEDTODO, TRUE);
						return DB_E_CANCELED;
					}
				}
			}
		}

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			if(bDeferred && pRow->m_status != DBPENDINGSTATUS_CHANGED &&
				pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
			{
				HRESULT hrNotify = pT->Fire_OnRowChange(pT, 1, &hNotifyRow,
					DBREASON_ROW_FIRSTCHANGE, DBEVENTPHASE_SYNCHAFTER, FALSE);

				if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
				{
					_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
					{
						pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
							rgColumns.GetData(), DBREASON_COLUMN_SET,
							DBEVENTPHASE_FAILEDTODO, TRUE);
					}
					return DB_E_CANCELED;
				}
			}
		}
	}


	DBORDINAL cCols;
	ATLCOLUMNINFO* pColInfo = T::GetColumnInfo(pT, &cCols);
	ATLENSURE_RETURN(pColInfo);
	for (DBORDINAL iBind =0; iBind < pBinding->cBindings; iBind++)
	{
		DBBINDING* pBindCur = &(pBinding->pBindings[iBind]);
		DBORDINAL iColInfo;
		for (iColInfo = 0;
			 iColInfo < cCols && pBindCur->iOrdinal != pColInfo[iColInfo].iOrdinal;
			 iColInfo++);
		if (iColInfo == cCols)
		{
			_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
			{
				if( !bReading )
				{
					SendColumnSetFailureNotification( pT, hNotifyRow, pBinding, rgColumns );
					SendRowsFirstChangeFailureNotification( pT, pRow, &hNotifyRow, bDeferred );
				}
			}
			return DB_E_BADORDINAL;
		}
		ATLCOLUMNINFO* pColCur = &(pColInfo[iColInfo]);
		// Ordinal found at iColInfo

		BYTE* pSrcTemp = (bReading) ? (BYTE*)pSrcData + pColCur->cbOffset :
									  (BYTE*)pSrcData + pBindCur->obValue;

		BYTE* pDstTemp = NULL;
		if (pBindCur->dwPart & DBPART_VALUE)
			pDstTemp = (bReading) ? (BYTE*)pDstData + pBindCur->obValue :
						 (BYTE*)pDstData + pColCur->cbOffset;


		if (!bReading)
		{
			// Check to see that the appropriate data parts are available
			if ((pBindCur->dwPart & DBPART_LENGTH) &&
				!(pBindCur->dwPart & DBPART_VALUE) &&
				!(pBindCur->dwPart & DBPART_STATUS))
			{
				_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
				{
					if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
						pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
					{
						pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
							rgColumns.GetData(), DBREASON_COLUMN_SET, DBEVENTPHASE_FAILEDTODO,
							TRUE);
					}
				}

				// Not sure why you would want to run SetData here!
				bFailed = true;
				continue;
			}
		}

		// Handle the the status for any consumer issues
		DBSTATUS dbStat = DBSTATUS_S_OK;
		if (bReading)
		{
			dbStat = pT->GetDBStatus(pRow, pColCur);

			if (dbStat == DBSTATUS_S_ISNULL)
			{
				if (pBindCur->dwPart & DBPART_STATUS)
					*((DBSTATUS*)((BYTE*)(pDstData) + pBindCur->obStatus)) = dbStat;

				// Set the length to 0 as reqiured by the spec.
				if (pBindCur->dwPart & DBPART_LENGTH)
					*((DBLENGTH*)((BYTE*)(pDstData) + pBindCur->obLength)) = 0;

				// Set the destination value to NULL
				if (pBindCur->dwPart & DBPART_VALUE)
				{

					ATLENSURE_RETURN(pDstTemp);
					*pDstTemp = NULL;
				}

				continue;
			}
		}
		else
		{
			// Allow the provider to do checking for DBSTATUS_S_ISNULL
			if (pBindCur->dwPart & DBPART_STATUS)
			{
				dbStat = *((DBSTATUS*)((BYTE*)(pSrcData) + pBindCur->obStatus));

				// Return DBSTATUS_E_UNAVAILABLE if the status is DBSTATUS_S_OK
				//	and either the value part is not bound or the length part is
				//	bound and the type is DBTYPE_BYTES.

				// There was another entry of code here with LENGTH, NO VALUE,
				//	and status was not DBSTATUS_S_ISNULL.  May need to regenerate that
				if (dbStat == DBSTATUS_S_OK)
				{
					if (!(pBindCur->dwPart & DBPART_VALUE) ||
						((pBindCur->dwPart & DBPART_LENGTH) && (pBindCur->wType == DBTYPE_BYTES)))
					{
						// Can't set non-null columns w/o a value part
						_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
						{
							if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
								pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
							{
								pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
									rgColumns.GetData(), DBREASON_COLUMN_SET, DBEVENTPHASE_FAILEDTODO,
									TRUE);
							}
						}

						bFailed = true;
						*((DBSTATUS*)((BYTE*)(pSrcData) + pBindCur->obStatus)) = DBSTATUS_E_UNAVAILABLE;
						continue;
					}
				}

				switch (dbStat)
				{
				case DBSTATUS_S_ISNULL:
					if (!(pColCur->dwFlags & DBCOLUMNFLAGS_ISNULLABLE) ||
						FAILED(pT->SetDBStatus(&dbStat, pRow, pColCur)))
					{
						_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
						{
							if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
								pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
							{
								pT->Fire_OnFieldChange(pT, hNotifyRow,
									pBinding->cBindings, rgColumns.GetData(),
									DBREASON_COLUMN_SET, DBEVENTPHASE_FAILEDTODO,
									TRUE);
							}
						}

						// Special processing for attempting to write, read-only columns
						if (!(pColCur->dwFlags & DBCOLUMNFLAGS_ISNULLABLE))
							*((DBSTATUS*)((BYTE*)(pSrcData) + pBindCur->obStatus)) = DBSTATUS_E_INTEGRITYVIOLATION;

						bFailed = true;
					}
					else
					{
						_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
						{
							if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
								pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
							{
								pT->Fire_OnFieldChange(pT, hNotifyRow, 1, &iBind,
									DBREASON_COLUMN_SET, DBEVENTPHASE_DIDEVENT, TRUE);
							}
						}
						bSucceeded = true;
						dbStat = DBSTATUS_S_OK;
						if (pBindCur->dwPart & DBPART_VALUE)
						{
							ATLENSURE_RETURN(pDstTemp);
							*pDstTemp = NULL;
						}
					}
					continue;
					break;
				case DBSTATUS_S_DEFAULT:
				case DBSTATUS_S_IGNORE:
				{
					HRESULT hrStatus = pT->SetDBStatus(&dbStat, pRow, pColCur);
					*((DBSTATUS*)((BYTE*)(pSrcData) + pBindCur->obStatus)) = dbStat;

					if (FAILED(hrStatus))
					{
						_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
						{
							if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
								pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
							{
								pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
									rgColumns.GetData(), DBREASON_COLUMN_SET,
									DBEVENTPHASE_FAILEDTODO, TRUE);
							}
						}

						// Note, status should be set by SetDBStatus
						bFailed = true;
					}
					else
					{
						_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
						{
							if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
								pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
							{
								pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
									rgColumns.GetData(), DBREASON_COLUMN_SET,
									DBEVENTPHASE_DIDEVENT, TRUE);
							}
						}
						bSucceeded = true;
					}
					continue;
					break;
				}
				case DBSTATUS_S_OK:
					// Still call SetDBStatus here as they may have locks on
					// integrity contstraints to observe
					if (FAILED(pT->SetDBStatus(&dbStat, pRow, pColCur)))
					{
						_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
						{
							if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
								pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
							{
								pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
									rgColumns.GetData(), DBREASON_COLUMN_SET,
									DBEVENTPHASE_FAILEDTODO, TRUE);
							}
						}

						bFailed = true;
						*((DBSTATUS*)((BYTE*)(pSrcData) + pBindCur->obStatus)) = dbStat;
						continue;
					}
					break;
				default:
					*((DBSTATUS*)((BYTE*)(pSrcData) + pBindCur->obStatus)) = DBSTATUS_E_BADSTATUS;

					_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
					{
						if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
							pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
						{
							pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
								rgColumns.GetData(), DBREASON_COLUMN_SET,
								DBEVENTPHASE_FAILEDTODO, TRUE);
						}
					}

					bFailed = true;
					continue;
					break;
				}
			}
		}

		// Determine sizes of input and output columns
		DBLENGTH cbCol = 0;
		DBLENGTH cbDst;
		if (bReading)
			cbDst = pBindCur->cbMaxLen;
		else
			cbDst = pColCur->ulColumnSize;

		switch (pColCur->wType)
		{
		case DBTYPE_STR:
			if (bReading)
				cbCol = static_cast<DBLENGTH>(AtlStrLen((LPSTR)(((BYTE*)pSrcData)) + pColCur->cbOffset));
			else
			{
				// Use the length field when setting data
				if (pBindCur->dwPart & DBPART_LENGTH)
					cbCol = *((DBLENGTH*)((BYTE*)(pSrcData) + pBindCur->obLength));
				else
					cbCol = static_cast<DBLENGTH>(AtlStrLen((LPSTR)(pSrcTemp)));	// was cbDst

				if (cbCol >= cbDst)
				{
					if (cbCol > (cbDst + 1)) // over maximum case
					{
						_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
						{
							if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
								pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
							{
								pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
									rgColumns.GetData(), DBREASON_COLUMN_SET,
									DBEVENTPHASE_FAILEDTODO, TRUE);
							}
						}

						bFailed = true;
						if (pBindCur->dwPart & DBPART_STATUS)
							*((DBSTATUS*)((BYTE*)(pSrcData) + pBindCur->obStatus)) = DBSTATUS_E_CANTCONVERTVALUE;
						continue;
					}
				}
				cbCol = cbDst;	// Leave room for NULL term. need to copy for WSTR
			}
			break;
		case DBTYPE_WSTR:
		case DBTYPE_BSTR:
			if (bReading)
				cbCol = static_cast<DBLENGTH>(AtlStrLen((LPWSTR)(((BYTE*)pSrcData) + pColCur->cbOffset))) * sizeof(WCHAR);
			else
			{
				if (pBindCur->dwPart & DBPART_LENGTH)
					cbCol = *((DBLENGTH*)((BYTE*)(pSrcData) + pBindCur->obLength));
				else
					cbCol = static_cast<DBLENGTH>(AtlStrLen((LPWSTR)(pSrcData))) * sizeof(WCHAR);

				if (cbCol >= cbDst)
				{
					if (cbCol > (cbDst + 1)) // over maximum case
					{
						_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
						{
							if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
								pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
							{
								pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
									rgColumns.GetData(), DBREASON_COLUMN_SET,
									DBEVENTPHASE_FAILEDTODO, TRUE);
							}
						}

						bFailed = true;
						if (pBindCur->dwPart & DBPART_STATUS)
							*((DBSTATUS*)((BYTE*)(pSrcData) + pBindCur->obStatus)) = DBSTATUS_E_CANTCONVERTVALUE;
						continue;
					}
				}
				cbCol = cbDst;	// Leave room for NULL term. need to copy for WSTR
			}
			break;
		case DBTYPE_BYTES:
			if (bReading)
				cbCol = pColCur->ulColumnSize;
			else
			{
				if (pBindCur->dwPart & DBPART_LENGTH)
					cbCol = *((DBLENGTH *)((BYTE*)(pSrcData) + pBindCur->obLength));
				else
				{
					_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
					{
						if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
							pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
						{
							pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
								rgColumns.GetData(), DBREASON_COLUMN_SET,
								DBEVENTPHASE_FAILEDTODO, TRUE);
						}
					}

					// If no length part is bound for DBTYPE_BYTES, it is an error
					bFailed = true;
					continue;
				}

				if (cbCol >= cbDst)
					cbCol = cbDst;	// Leave room for NULL term. need to copy for WSTR
			}
			break;
		default:
			if (bReading)
				cbCol = pColCur->ulColumnSize;
			else
				cbDst = pColCur->ulColumnSize;

			break;
		}
		cbDst=cbCol;

		// Handle cases where we have provider owned memory.  Note, these should be
		// with DBTYPE_BYREF (otherwise, it doesn't make sense).
		if (pBindCur->dwPart & DBPART_VALUE)
		{
			if (pBindCur->dwMemOwner == DBMEMOWNER_PROVIDEROWNED
				&& pBindCur->wType & DBTYPE_BYREF)
			{
				ATLENSURE_RETURN(pDstTemp);
				*(BYTE**)pDstTemp = pSrcTemp;
			}
			else
			{
				ATLASSERT(pT->m_spConvert != NULL);
				ATLASSUME(pDstTemp != NULL);

				hr = pT->m_spConvert->DataConvert(pColCur->wType, pBindCur->wType,
						cbCol, &cbDst, pSrcTemp, pDstTemp, pBindCur->cbMaxLen,
						dbStat, &dbStat, pBindCur->bPrecision, pBindCur->bScale,0);
			}
		}
		if (pBindCur->dwPart & DBPART_LENGTH)
		{
			if (bReading)
				*((DBLENGTH*)((BYTE*)(pDstData) + pBindCur->obLength)) = (dbStat == DBSTATUS_S_ISNULL) ? 0 : cbDst;
			else
				*((DBLENGTH*)((BYTE*)(pSrcData) + pBindCur->obLength)) = cbDst;
		}
		if (pBindCur->dwPart & DBPART_STATUS)
		{
			if (bReading)
				*((DBSTATUS*)((BYTE*)(pDstData) + pBindCur->obStatus)) = dbStat;
			else
				*((DBSTATUS*)((BYTE*)(pSrcData) + pBindCur->obStatus)) = dbStat;
		}

		if (FAILED(hr))
		{
			if (!bReading)
			{
				_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
				{
					if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
						pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
					{
						pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
							rgColumns.GetData(), DBREASON_COLUMN_SET,
							DBEVENTPHASE_FAILEDTODO, TRUE);
					}
				}
			}

			bFailed = true;
		}
		else
		{
			bSucceeded = true;
		}
	}

	// Return error codes to the consumer
	if (bFailed)
	{
		_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
		{
			if( !bReading )
			{
//				SendColumnSetFailureNotification( pT, hNotifyRow, pBinding, rgColumns );
				SendRowsFirstChangeFailureNotification( pT, pRow, &hNotifyRow, bDeferred );
			}
		}
		return (bSucceeded != false) ? DB_S_ERRORSOCCURRED : DB_E_ERRORSOCCURRED;
	}
	else
	{
		if (!bReading)
		{
			_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
			{
				if (/* pRow->m_status != DBPENDINGSTATUS_NEW && */
					pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
				{
					pT->Fire_OnFieldChange(pT, hNotifyRow, pBinding->cBindings,
						rgColumns.GetData(), DBREASON_COLUMN_SET,
						DBEVENTPHASE_DIDEVENT, TRUE);
				}
			}
		}

		return hr;
	}
}

/* Function detection for GetRowsAt. Signature is:

STDMETHOD (GetRowsAt)(
_In_ HWATCHREGION hReserved1,
_In_ HCHAPTER hReserved2,
_In_ DBBKMARK cbBookmark,
_In_ const BYTE* pBookmark,
_In_ DBROWOFFSET lRowsOffset,
_In_ DBROWCOUNT cRows,
_Out_ DBCOUNTITEM* pcRowsObtained,
_Outptr_ HROW** prghRows)
*/
template<class T, class = void>
struct Has_GetRowsAt : ::std::false_type {};

template<class T>
struct Has_GetRowsAt<T, ::std::void_t<
	decltype(::std::declval<T>().GetRowsAt(
		HWATCHREGION{},
		HCHAPTER{},
		DBBKMARK{},
		::std::declval<const BYTE*>(),
		DBROWOFFSET{},
		DBROWCOUNT{},
		::std::declval<DBCOUNTITEM*>(),
		::std::declval<HROW**>())) >> : ::std::true_type {};

template <class T, class Storage,
		  class BaseInterface = IRowsetChange,
		  class RowClass = CSimpleRow,
		  class MapClass = CAtlMap < RowClass::KeyType, RowClass* > >
class ATL_NO_VTABLE IRowsetChangeImpl :
	public BaseInterface
{
public:
	// IRowsetChange Methods
	STDMETHOD (DeleteRows)(
		_In_ HCHAPTER /*hReserved*/,
		_In_ DBCOUNTITEM cRows,
		_In_reads_(cRows) const HROW rghRows[],
		_Out_writes_opt_(cRows) DBROWSTATUS rgRowStatus[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetChangeImpl::DeleteRows"));

		T* pT = (T*)this;
		typename T::ObjectLock lock(pT);

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();
		}

		BOOL bSuccess = false;
		BOOL bFailed = false;
		// Check to see if the DBPROP_UPDATABILITY value DBPROPVAL_UP_CHANGE
		// is set.  If not, then this method should return DB_E_NOTSUPPORTED.
		CComVariant varChange;
		HRESULT hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_UPDATABILITY,
							&varChange);
		if (!(varChange.iVal & DBPROPVAL_UP_DELETE))
			return DB_E_NOTSUPPORTED;

		// NO-OP if cRows is zero
		if (cRows == 0)
			return S_OK;

		if (rghRows == NULL && cRows >= 1)
			return E_INVALIDARG;

		// Determine if we're in immediate or deferred mode
		CComVariant varDeferred;
		bool bDeferred;
		hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_IRowsetUpdate,
							&varDeferred);
		(FAILED(hr) || varDeferred.boolVal == ATL_VARIANT_FALSE) ? bDeferred = false : bDeferred = true;


		// Loop through and delete rows
		for (DBCOUNTITEM l=0; l<cRows; l++)
		{
			HROW hRow = rghRows[l];
			RowClass* pRow;
			DBROWSTATUS rowStat = DBROWSTATUS_S_OK;

			// Handle events
			_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
			{
				HRESULT hrNotify = pT->Fire_OnRowChange(pT, 1, &rghRows[l],
					DBREASON_ROW_DELETE, DBEVENTPHASE_OKTODO, FALSE);
				if (hrNotify == S_FALSE)
				{
					bFailed |= true;
					if (rgRowStatus != NULL)
						rgRowStatus[l] = DBROWSTATUS_E_CANCELED;
					continue;
				}
				else
				{
					hrNotify = pT->Fire_OnRowChange(pT, 1, &rghRows[l],
						DBREASON_ROW_DELETE, DBEVENTPHASE_ABOUTTODO, FALSE);
					if (hrNotify == S_FALSE)
					{
						bFailed |= true;
						if (rgRowStatus != NULL)
							rgRowStatus[l] = DBROWSTATUS_E_CANCELED;

						continue;
					}
				}

				// Send sync after now as it gets tricky to replace stuff once
				// we're in the method.
				hrNotify = pT->Fire_OnRowChange(pT, 1, &rghRows[l],
					DBREASON_ROW_DELETE, DBEVENTPHASE_SYNCHAFTER, FALSE);
				if (hrNotify == S_FALSE)
				{
					bFailed |= true;
					if (rgRowStatus != NULL)
						rgRowStatus[l] = DBROWSTATUS_E_CANCELED;

					continue;
				}
			}

			// Attempt to locate the row in our map
			bool bFound = pT->m_rgRowHandles.Lookup(hRow, pRow);
			if (!bFound || pRow == NULL)
			{
				bFailed |= true;
				if (rgRowStatus != NULL)
					rgRowStatus[l] = DBROWSTATUS_E_INVALID;
				continue;
			}

			// Check if the row has already been deleted.  Note, we
			// use DBPENDINGSTATUS_DELETED in the immediate case as
			// well.
			if (pRow->m_status == DBPENDINGSTATUS_DELETED)
			{
				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					pT->Fire_OnRowChange(pT, 1, &rghRows[l],
						DBREASON_ROW_DELETE, DBEVENTPHASE_FAILEDTODO,
						TRUE);
				}

				if (rgRowStatus != NULL)
					rgRowStatus[l] = DBROWSTATUS_E_DELETED;
				bFailed |= true;
				continue;
			}


			// Delete the row
			if (bDeferred)
			{
				// Mark the row as deleted but do not remove it until
				// IRowsetUpdate::Update is called.
				if (pRow->m_status != DBPENDINGSTATUS_INVALIDROW)
				{
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						pT->Fire_OnRowChange(pT, 1, &rghRows[l], DBREASON_ROW_DELETE,
							DBEVENTPHASE_DIDEVENT, FALSE);
					}
					bSuccess |= true;
					rowStat = DBROWSTATUS_S_OK;
				}
				else
				{
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						pT->Fire_OnRowChange(pT, 1, &rghRows[l],
							DBREASON_ROW_DELETE, DBEVENTPHASE_FAILEDTODO,
							FALSE);
					}

					bFailed |= true;
					// unsigned high bit signified neg. number
					if (pRow->m_dwRef & 0x80000000)
						rowStat = DBROWSTATUS_E_INVALID;
					else
						rowStat = DBROWSTATUS_E_DELETED;
				}
			}
			else
			{
				// Remove the m_rgRowData and m_rgRowLink entries.  The
				// HROW will be released in IRowset::ReleaseRows.
				// Remove the link by NULLing out the pointer
				if( pRow->m_iRowset < 0 || pRow->m_iRowset >= pT->m_rgRowData.GetCount() )
				{
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						pT->Fire_OnRowChange(pT, 1, &rghRows[l],
							DBREASON_ROW_DELETE, DBEVENTPHASE_FAILEDTODO,
							FALSE);
					}

					rowStat = DBROWSTATUS_E_INVALID;
					bFailed |= true;
				}
				else
				{
					pT->m_rgRowData.RemoveAt(pRow->m_iRowset);

					// Perform the actual delete of the row.  Send notifications
					// to inform the consumer of the change.

					// Need to update any outstanding pRow->m_iRowset
					// variables
					POSITION pos = pT->m_rgRowHandles.GetStartPosition();
					while (pos != NULL)
					{
						typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext( pos );
						ATLENSURE_RETURN( pPair != NULL );
						RowClass* pCheckRow = pPair->m_value;
						if (pCheckRow != NULL &&
							pCheckRow->m_iRowset > pRow->m_iRowset)
							pCheckRow->m_iRowset--;
					}
#ifndef _ATL_IF_EXISTS_FALLBACK
					if constexpr(Has_GetRowsAt<T>::value)
#else
					__if_exists(T::GetRowsAt)
#endif
					{
						// If we support bookmarks, then ensure our bookmark array
						// is solid
						for (size_t k=0; k<pT->m_rgBookmarks.GetCount(); k++)
						{
							if (pT->m_rgBookmarks[k] == (DBROWCOUNT)(pRow->m_iRowset + 1))
								pT->m_rgBookmarks[k] = -1;		// Value for invalid bookmark

							if (pT->m_rgBookmarks[k] > (DBROWCOUNT)(pRow->m_iRowset + 1))
								pT->m_rgBookmarks[k] = (pT->m_rgBookmarks[k] - 1);
						}
					}

					if (FAILED(pT->FlushData(rghRows[l], NULL)))
					{
						_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
						{
							pT->Fire_OnRowChange(pT, 1, &rghRows[l],
								DBREASON_ROW_DELETE, DBEVENTPHASE_FAILEDTODO,
								FALSE);
						}
						bFailed |= true;
						rowStat = DBROWSTATUS_E_FAIL;
					}
					else
					{
						// Send DIDEVENT
						_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
						{
							pT->Fire_OnRowChange(pT, 1, &rghRows[l],
								DBREASON_ROW_DELETE, DBEVENTPHASE_DIDEVENT, FALSE);
						}
						rowStat = DBROWSTATUS_S_OK;
						bSuccess |= true;
					}
				}
			}

			// We use the status even in immediate mode to determine if a
			// row has been deleted from the cache but not release
			if (pRow->m_status == DBPENDINGSTATUS_NEW)
				pRow->m_status = DBPENDINGSTATUS_INVALIDROW;
			else
			{
				if (pRow->m_status != DBPENDINGSTATUS_INVALIDROW)
					pRow->m_status = DBPENDINGSTATUS_DELETED;
			}


			if (rgRowStatus != NULL)
				rgRowStatus[l] = rowStat;
		}



		hr = S_OK;
		if (bFailed)
			(bSuccess) ? hr = DB_S_ERRORSOCCURRED : hr = DB_E_ERRORSOCCURRED;

		return hr;
	}

	STDMETHOD (SetData)(
		_In_ HROW hRow,
		_In_ HACCESSOR hAccessor,
		_In_opt_ void* pSrcData)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetChangeImpl::SetData\n"));

		T* pT = (T*)this;
		typename T::ObjectLock lock(pT);

		_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();
		}

		RowClass* pRow;

		// Check to see if the DBPROP_UPDATABILITY value DBPROPVAL_UP_CHANGE
		// is set.  If not, then this method should return DB_E_NOTSUPPORTED.
		CComVariant varChange;
		HRESULT hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_UPDATABILITY,
							&varChange);
		if (!(varChange.iVal & DBPROPVAL_UP_CHANGE))
			return DB_E_NOTSUPPORTED;


		if (hRow == NULL)
			return DB_E_BADROWHANDLE;
		if( ! pT->m_rgRowHandles.Lookup(hRow, pRow) )
			return DB_E_BADROWHANDLE;
		if (hRow == NULL || pRow == NULL)
			return DB_E_BADROWHANDLE;
		hr = TransferData<T, RowClass, MapClass>
								 (pT, false, pSrcData, pRow, &(pT->m_rgRowHandles), hAccessor);
		if (FAILED(hr))
			return hr;

		// Flush Users Data
		HRESULT hrFlush = pT->FlushData(hRow, hAccessor);

		if (SUCCEEDED(hrFlush))
			return hr;	// note: we could have DB_S_ERRORSOCCURRED from TransferData
		else
			return hrFlush;
	}

	STDMETHOD (InsertRow)(
		_In_ HCHAPTER /*hReserved*/,
		_In_ HACCESSOR hAccessor,
		_In_opt_ void* pData,
		_Out_opt_ HROW* phRow)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetChangeImpl::InsertRow\n"));

		T* pT = (T*) this;
		typename T::ObjectLock lock(pT);
		HRESULT hrNotify = S_OK;	// Used for responses to notifications

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
			{
				// Note, we can't set this above because we may inadvertantly
				// step on someone elses *phRow
				if (phRow != NULL)
					*phRow = NULL;
				return DB_E_NOTREENTRANT;
			}
			else
				pT->DecrementMutex();
		}

		// Check to see if the DBPROP_UPDATABILITY value DBPROPVAL_UP_CHANGE
		// is set.  If not, then this method should return DB_E_NOTSUPPORTED.
		CComVariant varChange;
		HRESULT hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_UPDATABILITY,
							&varChange);
		if (!(varChange.iVal & DBPROPVAL_UP_INSERT))
			return DB_E_NOTSUPPORTED;


		if (phRow != NULL)
			*phRow = NULL;

		// validate that the hAccessor is valid
		typename T::_BindType* pBinding;
		bool bFound = pT->m_rgBindings.Lookup((INT_PTR)hAccessor, pBinding);
		if (!bFound || pBinding == NULL)
			return DB_E_BADACCESSORHANDLE;

		// validate parameters
		if (pData == NULL && pBinding->cBindings != 0)
			return E_INVALIDARG;

		// Check to see if DBPROP_CANHOLDROWS is set to false.  In this case,
		// return a DB_E_ROWSNOTRELEASED if there are any pending changes.
		CComVariant varHoldRows;
		hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_CANHOLDROWS,
			&varHoldRows);

		if (FAILED(hr) || varHoldRows.boolVal == ATL_VARIANT_FALSE)
		{
			if (pT->m_rgRowHandles.GetCount() > 0)
			{
				RowClass* pCheckRow = NULL;
				POSITION pos = pT->m_rgRowHandles.GetStartPosition();

				while (pos != NULL)
				{
					typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext(pos);
					ATLENSURE_RETURN( pPair != NULL );
					HROW hCheckRow = pPair->m_key;
					bool bFoundHandle = pT->m_rgRowHandles.Lookup(hCheckRow, pCheckRow);

					if (bFoundHandle && pCheckRow != NULL &&
						pCheckRow->m_status != DBPENDINGSTATUS_UNCHANGED)
						return DB_E_ROWSNOTRELEASED;
				}
			}
		}

		// We should check DBPROP_IMMOBILEROWS and then call an
		//		ordering routine in the user's code.
		// Create a row and place into m_rgRowData
		Storage newRow;		// Create an instance of the users data
		CComVariant var;
		HRESULT hrProps = pT->GetPropValue(&DBPROPSET_ROWSET,
			DBPROP_BOOKMARKS, &var);
		if (SUCCEEDED(hrProps) && var.boolVal != ATL_VARIANT_FALSE)
		{
			DBORDINAL cCols;
			ATLCOLUMNINFO* pInfo = T::GetColumnInfo(pT, &cCols);
			ATLASSERT(pInfo != NULL);
			for (DBORDINAL i = 0;i < cCols; i++)
			{
				if (pInfo[i].iOrdinal == 0)
				{
					switch(pInfo[i].wType)
					{
					case DBTYPE_BYTES:
						*((DBBKMARK*)(&newRow + pInfo[i].cbOffset)) = (ULONG_PTR)(pT->m_rgRowData.GetCount() + 1);
						break;
					default:
						ATLASSERT(FALSE);
					};
				}
			}
		}

		// Call CreateRow to make a new hRow
		HROW hInsertedRow = NULL;
		DBCOUNTITEM ulRowsObtained = 0;

		size_t lSize = pT->m_rgRowData.GetCount();

		hr = pT->CreateRow((LONG_PTR)lSize, ulRowsObtained, &hInsertedRow);

		if (FAILED(hr))
		{
			return hr;
		}
		else
		{
			_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
			{
				hrNotify = pT->Fire_OnRowChange(pT, 1, &hInsertedRow, DBREASON_ROW_INSERT,
					DBEVENTPHASE_OKTODO, FALSE);
				if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
				{
					pT->m_rgRowHandles.RemoveKey((typename RowClass::KeyType)hInsertedRow);
					return DB_E_CANCELED;
				}

				hrNotify = pT->Fire_OnRowChange(pT, 1, &hInsertedRow, DBREASON_ROW_INSERT,
					DBEVENTPHASE_ABOUTTODO, FALSE);
				if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
				{
					pT->m_rgRowHandles.RemoveKey((typename RowClass::KeyType)hInsertedRow);
					return DB_E_CANCELED;
				}
			}
		}

		// Add in the storage and linkeage for the row
		CComVariant varOrderedInsert;

		// Need to determine if we want ordered insertions
		bool bOrderedInsert;
		hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_IMMOBILEROWS,
							&varOrderedInsert);
		(FAILED(hr) || varOrderedInsert.boolVal != ATL_VARIANT_FALSE) ?
			bOrderedInsert = false : bOrderedInsert = true;

		//if (!pT->m_rgRowData.Add(newRow))
		//{
		//	ATLTRACE(atlTraceDBProvider, 0, _T("Failed to add record Out of Memory"));
		//	return E_OUTOFMEMORY;
		//}
		_ATLTRY
		{
			pT->m_rgRowData.Add(newRow);
		}
		_ATLCATCH( e )
		{
			_ATLDELETEEXCEPTION( e );
			ATLTRACE(atlTraceDBProvider, 0, _T("Failed to add record Out of Memory"));
			return E_OUTOFMEMORY;
		}

		// Set the inserted row's status to DBPENDINGSTATUS_NEW if it is deferred.
		// This will prevent a spurious Notification for ROW_FIRSTCHANGE from
		// firing in IRowsetUpdateImpl::SetData.
		CComVariant varDeferred;
		bool bDeferred;
		HRESULT hrDeferred = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_IRowsetUpdate,
							&varDeferred);
		(FAILED(hrDeferred) || varDeferred.boolVal == ATL_VARIANT_FALSE) ? bDeferred = false : bDeferred = true;

		RowClass* pRow;
		ATLVERIFY( pT->m_rgRowHandles.Lookup(hInsertedRow, pRow) );
		ATLENSURE_RETURN(pRow != NULL);

		// Set the status to NEW + UNCHANGED so we don't generate COLUMN_SET or
		// FIRSTCHANGE events in SetData. We'll switch it back to zero after the
		// operation.
		pRow->m_status = DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED;

		// Call SetData, if necessary
		if (pData != NULL)
		{
			hr = SetData(hInsertedRow, hAccessor, pData);
			if (FAILED(hr))
			{
				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					pT->Fire_OnRowChange(pT, 1, &hInsertedRow, DBREASON_ROW_INSERT,
						DBEVENTPHASE_FAILEDTODO, TRUE);
				}
			}
		}

		if (!bDeferred)
			pRow->m_status = 0;
		else
			pRow->m_status = DBPENDINGSTATUS_NEW;

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			hrNotify = pT->Fire_OnRowChange(pT, 1, &hInsertedRow, DBREASON_ROW_INSERT,
				DBEVENTPHASE_SYNCHAFTER, FALSE);
			if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
			{
				// Remove storage
				RowClass* pRowInserted;
				ATLVERIFY( pT->m_rgRowHandles.Lookup(hInsertedRow, pRowInserted) );
				ATLENSURE_RETURN( pRowInserted != NULL );
				pT->m_rgRowData.RemoveAt(pRowInserted->m_iRowset);

				// Remove Handle
				pT->m_rgRowHandles.RemoveKey((typename RowClass::KeyType)hInsertedRow);

				return DB_E_CANCELED;
			}

			pT->Fire_OnRowChange(pT, 1, &hInsertedRow, DBREASON_ROW_INSERT,
				DBEVENTPHASE_DIDEVENT, TRUE);
		}

		if (phRow != NULL && SUCCEEDED(hr))
			*phRow = hInsertedRow;

		return hr;
 	}

	// Callbacks to Provider
	HRESULT FlushData(
		_In_ HROW,
		_In_ HACCESSOR)
	{
		// The provider overrides this function to commit data to its store
		return S_OK;
	}
};


// IRowsetImpl
template <class T, class RowsetInterface,
		  class RowClass = CSimpleRow,
		  class MapClass = CAtlMap < RowClass::KeyType, RowClass* > >
class ATL_NO_VTABLE IRowsetImpl :
	public RowsetInterface
{
public:
	typedef RowClass _HRowClass;
	IRowsetImpl()
	{
		m_iRowset = 0;
		m_bCanScrollBack = false;
		m_bCanFetchBack = false;
		m_bRemoveDeleted = true;
		m_bIRowsetUpdate = false;
		m_bReset = true;
		m_bExternalFetch = false;
	}
	virtual ~IRowsetImpl()
	{
		//for (int i = 0; i < m_rgRowHandles.GetCount(); i++)
		//	delete (m_rgRowHandles.GetValueAt(i));
		POSITION pos = m_rgRowHandles.GetStartPosition();
		while( pos != NULL )
		{
			typename MapClass::CPair *pPair = m_rgRowHandles.GetNext(pos);
			if(pPair!=NULL)
			{
				delete pPair->m_value;
			}
			else
			{
				ATLASSERT(FALSE);
			}
		}
	}
	HRESULT RefRows(
		_In_ DBCOUNTITEM cRows,
		_In_reads_(cRows) const HROW rghRows[],
		_Out_writes_opt_(cRows) DBREFCOUNT rgRefCounts[],
		_Out_writes_opt_(cRows) DBROWSTATUS rgRowStatus[],
		_In_ BOOL bAdd)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetImpl::AddRefRows\n"));
		if (cRows == 0)
			return S_OK;
		if (rghRows == NULL)
			return E_INVALIDARG;
		typename T::ObjectLock cab((T*)this);
		BOOL bSuccess1 = FALSE;
		BOOL bFailed1 = FALSE;
		DBROWSTATUS rs;
		DWORD dwRef;

		// Maintain an array of handles w/ zero ref counts for notification
		CAtlArray<HROW>  arrZeroHandles;

		for (ULONG iRow = 0; iRow < cRows; iRow++)
		{
			HROW hRowCur = rghRows[iRow];
			RowClass* pRow;
			bool bFoundCur = m_rgRowHandles.Lookup((typename RowClass::KeyType)hRowCur, pRow);
			if (!bFoundCur || pRow == NULL)
			{
				ATLTRACE(atlTraceDBProvider, 0, _T("Could not find HANDLE %x in list\n"), hRowCur);
				rs = DBROWSTATUS_E_INVALID;
				dwRef = 0;
				bFailed1 = TRUE;
			}
			else
			{

				if (pRow->m_status != DBPENDINGSTATUS_UNCHANGED &&
					pRow->m_status != DBPENDINGSTATUS_INVALIDROW &&
					pRow->m_dwRef == 0 && !bAdd)
				{
					if (rgRefCounts)
						rgRefCounts[iRow] = 0;
					if (rgRowStatus != NULL)
						rgRowStatus[iRow] = DBROWSTATUS_E_INVALID;
					bFailed1 = TRUE;
					continue;
				}

				// Check if we're in immediate or deferred mode
				CComVariant varDeferred;
				bool bDeferred;
				T* pT = (T*)this;
				HRESULT hr = pT->GetPropValue(&DBPROPSET_ROWSET,
					DBPROP_IRowsetUpdate, &varDeferred);
				(FAILED(hr) || varDeferred.boolVal == ATL_VARIANT_FALSE) ?
					bDeferred = false : bDeferred = true;

				if (!bDeferred && bAdd &&
					pRow->m_status == DBPENDINGSTATUS_DELETED)
				{
					bFailed1 = TRUE;
					if (rgRowStatus != NULL)
						rgRowStatus[iRow] = DBROWSTATUS_E_DELETED;
					continue;
				}

				if (bAdd)
					dwRef = pRow->AddRefRow();
				else
				{
					dwRef = pRow->ReleaseRow();
					if ((pRow->m_status != DBPENDINGSTATUS_UNCHANGED &&
						pRow->m_status != 0 &&
						pRow->m_status != DBPENDINGSTATUS_INVALIDROW) &&
						bDeferred)
					{
						if (rgRefCounts)
							rgRefCounts[iRow] = dwRef;
						if (rgRowStatus != NULL)
							rgRowStatus[iRow] = DBROWSTATUS_S_PENDINGCHANGES;
						bSuccess1 = TRUE;
						continue;
					}

					if (dwRef == 0)
					{
						_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
						{
							_ATLTRY
							{
								arrZeroHandles.Add(hRowCur);
							}
							_ATLCATCH( e )
							{
								_ATLDELETEEXCEPTION( e );
								return E_FAIL;
							}
						}

						// Now determine if the DBPROP_REMOVEDELETED property
						// is ATL_VARIANT_FALSE.  If so, then do NOT remove the
						// row.
						hr = pT->GetPropValue(&DBPROPSET_ROWSET,
							DBPROP_REMOVEDELETED, &varDeferred);
						if (FAILED(hr) || varDeferred.boolVal != ATL_VARIANT_FALSE)
						{
							delete pRow;
							m_rgRowHandles.RemoveKey((typename RowClass::KeyType)hRowCur);
						}
					}
				}
				bSuccess1 = TRUE;
				rs = DBROWSTATUS_S_OK;
			}
			if (rgRefCounts)
				rgRefCounts[iRow] = dwRef;
			if (rgRowStatus != NULL)
				rgRowStatus[iRow] = rs;
		}

		_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
		{
			if (!bAdd && arrZeroHandles.GetCount() > 0)
			{
				T* pT = (T*)this;
				pT->Fire_OnRowChange(pT, (ULONG_PTR)arrZeroHandles.GetCount(), arrZeroHandles.GetData(),
					DBREASON_ROW_RELEASE, DBEVENTPHASE_DIDEVENT, FALSE);
			}
		}

		if (!bSuccess1 && !bFailed1)
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("IRowsetImpl::RefRows Unexpected state\n"));
			return E_FAIL;
		}
		HRESULT hr = S_OK;
		if (bSuccess1 && bFailed1)
			hr = DB_S_ERRORSOCCURRED;
		if (!bSuccess1 && bFailed1)
			hr = DB_E_ERRORSOCCURRED;
		return hr;
	}

	STDMETHOD(AddRefRows)(
		_In_ DBCOUNTITEM cRows,
		_In_reads_(cRows) const HROW rghRows[],
		_Out_writes_opt_(cRows) DBREFCOUNT rgRefCounts[],
		_Out_writes_opt_(cRows) DBROWSTATUS rgRowStatus[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetImpl::AddRefRows\n"));
		if (cRows == 0)
			return S_OK;
		return RefRows(cRows, rghRows, rgRefCounts, rgRowStatus, TRUE);
	}
	virtual DBSTATUS GetDBStatus(
		_In_ RowClass*,
		_In_ ATLCOLUMNINFO*)
	{
		return DBSTATUS_S_OK;
	}
	virtual HRESULT SetDBStatus(
		_In_opt_ DBSTATUS*,
		_In_opt_ RowClass*,
		_In_opt_ ATLCOLUMNINFO*)
	{
		// The provider overrides this function to handle special processing
		// for DBSTATUS_S_ISNULL and DBSTATUS_S_DEFAULT.
		return S_OK;
	}

	OUT_OF_LINE HRESULT GetDataHelper(
		_In_ HACCESSOR hAccessor,
		_Out_ ATLCOLUMNINFO*& rpInfo,
		_Outptr_ void** ppBinding,
		_Out_ void*& rpSrcData,
		_Out_ DBORDINAL& rcCols,
		_Inout_ CComPtr<IDataConvert>& rspConvert,
		_In_ RowClass* pRow)
	{
		ATLENSURE_RETURN(ppBinding != NULL);
		T* pT = (T*) this;
		typename T::_BindingVector::CPair* pPair = pT->m_rgBindings.Lookup( hAccessor );
		if (pPair == NULL || pPair->m_value == NULL)
			return DB_E_BADACCESSORHANDLE;
		*ppBinding = pPair->m_value;
		rpSrcData = (void*)&(pT->m_rgRowData[pRow->m_iRowset]);
		rpInfo = T::GetColumnInfo((T*)this, &rcCols);
		rspConvert = pT->m_spConvert;
		return S_OK;

	}
	STDMETHOD(GetData)(
		_In_ HROW hRow,
		_In_ HACCESSOR hAccessor,
		_In_opt_ void *pDstData)
	{
		T* pT = (T*)this;
		RowClass* pRow;
		if (hRow == NULL )
			return DB_E_BADROWHANDLE;

		if( !pT->m_rgRowHandles.Lookup((INT_PTR)hRow, pRow))
			return DB_E_BADROWHANDLE;

		if (pRow == NULL)
			return DB_E_BADROWHANDLE;

		return TransferData<T, RowClass, MapClass>
						   (pT, true, pDstData, pRow, &(pT->m_rgRowHandles), hAccessor);
	}

	HRESULT CreateRow(
		_In_ DBROWOFFSET lRowsOffset,
		_Inout_ DBCOUNTITEM& cRowsObtained,
		_Inout_ HROW* rgRows)
	{
		RowClass* pRow = NULL;
		ATLASSERT(lRowsOffset >= 0);
		typename RowClass::KeyType key = lRowsOffset+1;
		ATLASSERT(key > 0);
		bool bFound = m_rgRowHandles.Lookup(key,pRow);
		if (!bFound || pRow == NULL)
		{
			pRow = _ATL_NEW RowClass(lRowsOffset);
			if (pRow == NULL)
				return E_OUTOFMEMORY;
			_ATLTRY
			{
				m_rgRowHandles.SetAt(key, pRow);
			}
			_ATLCATCH( e )
			{
				_ATLDELETEEXCEPTION( e );
				delete pRow;
				pRow = NULL;
				return E_OUTOFMEMORY;
			}
		}
		pRow->AddRefRow();
		m_bReset = false;
		rgRows[cRowsObtained++] = (HROW)key;
		return S_OK;
	}

	HRESULT GetNextRowsSkipDeleted(
		_In_ HCHAPTER /*hReserved*/,
		_In_ DBROWOFFSET lRowsOffset,
		_In_ DBROWCOUNT cRows,
		_Out_ DBCOUNTITEM *pcRowsObtained,
		_Outptr_result_buffer_(*pcRowsObtained) HROW **prghRows)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetImpl::GetNextRows\n"));
		T* pT = (T*) this;

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
			{
				// Note, we can't set this above this block because we may
				// inadvertantly reset somebody else's pcRowsObtained
				if (pcRowsObtained != NULL)
					*pcRowsObtained = 0;
				return DB_E_NOTREENTRANT;
			}
			else
				pT->DecrementMutex();
		}

		if (pcRowsObtained != NULL)
			*pcRowsObtained = 0;
		if (prghRows == NULL || pcRowsObtained == NULL)
			return E_INVALIDARG;
		if (cRows == 0)
			return S_OK;
		HRESULT hr = S_OK;
		typename T::ObjectLock cab(pT);
		if (lRowsOffset < 0 && !m_bCanScrollBack)
			return DB_E_CANTSCROLLBACKWARDS;
		if (cRows < 0  && !m_bCanFetchBack)
			return DB_E_CANTFETCHBACKWARDS;

		DBROWOFFSET cRowsInSet = (DBROWOFFSET)pT->m_rgRowData.GetCount();

		DBROWOFFSET iStepSize = cRows >= 0 ? 1 : -1;
		// If cRows == MINLONG_PTR, we can't use ABS on it.  Therefore, we reset it
		// to a value just greater than cRowsInSet
		if (cRows == MINLONG_PTR && cRowsInSet != MINLONG_PTR)
			cRows = cRowsInSet + 2;	// set the value to something we can deal with
		else
			cRows = AbsVal(cRows);

		// First, simulate the operation, skipping over any deleted rows, calculate the number of rows retrieved,
		// and return an error code if appropriate

		DBROWOFFSET nCurrentRow = m_iRowset;

		// Note, if m_bReset, m_iRowset must be 0
		if ( m_bReset && (lRowsOffset < 0 || ( lRowsOffset == 0 && iStepSize < 0 ) ) )
			nCurrentRow = cRowsInSet;

		// skip the rows according to the lRowsOffset value
		if( lRowsOffset > 0 )
		{
			DBROWOFFSET nRowsToSkip = lRowsOffset;

			while( nRowsToSkip > 0 && nCurrentRow <= cRowsInSet )
			{
				RowClass* pRow = NULL;
				typename RowClass::KeyType key = nCurrentRow + 1;
				bool bFound = m_rgRowHandles.Lookup(key,pRow);
				if( bFound && pRow != NULL )
				{
					if( pRow->m_status == DBPENDINGSTATUS_DELETED )
					{
						nCurrentRow++;
						continue;
					}
				}
				nCurrentRow++;
				nRowsToSkip--;
			}

			if( nCurrentRow > cRowsInSet )
				return DB_S_ENDOFROWSET;
		}
		else if( lRowsOffset < 0 )
		{
			DBROWOFFSET nRowsToSkip = lRowsOffset;
			if (nRowsToSkip == MINLONG_PTR && cRowsInSet != MINLONG_PTR)
				nRowsToSkip = cRowsInSet + 2;	// set the value to something we can deal with
			else
				nRowsToSkip = -nRowsToSkip;

			while( nRowsToSkip > 0 && nCurrentRow > 0 )
			{
				nCurrentRow--;

				RowClass* pRow = NULL;
				typename RowClass::KeyType key = nCurrentRow + 1;
				bool bFound = m_rgRowHandles.Lookup(key,pRow);
				if( bFound && pRow != NULL )
				{
					if( pRow->m_status == DBPENDINGSTATUS_DELETED )
					{
						continue;
					}
				}
				nRowsToSkip--;
			}

			if( nCurrentRow < 0 )
				return DB_S_ENDOFROWSET;
		}

		DBROWOFFSET nFetchStartPosition = nCurrentRow;

		// now fetch the rows
		DBROWOFFSET cRowsToFetch = cRows;
		DBROWOFFSET cRowsFetched = 0;
		if( iStepSize == 1 )
		{
			while( cRowsToFetch > 0 && nCurrentRow < cRowsInSet )
			{
				RowClass* pRow = NULL;
				typename RowClass::KeyType key = nCurrentRow + 1;
				bool bFound = m_rgRowHandles.Lookup(key,pRow);
				if( bFound && pRow != NULL )
				{
					if( pRow->m_status == DBPENDINGSTATUS_DELETED )
					{
						nCurrentRow++;
						continue;
					}
				}
				// now we would fetch the row
				cRowsFetched++;
				cRowsToFetch--;
				nCurrentRow++;
			}
		}
		else
		{
			while( cRowsToFetch > 0 && nCurrentRow > 0 )
			{
				nCurrentRow--;
				RowClass* pRow = NULL;
				typename RowClass::KeyType key = nCurrentRow + 1;
				bool bFound = m_rgRowHandles.Lookup(key,pRow);
				if( bFound && pRow != NULL )
				{
					if( pRow->m_status == DBPENDINGSTATUS_DELETED )
					{
						continue;
					}
				}
				// now we would fetch the row
				cRowsFetched++;
				cRowsToFetch--;
			}
		}

		//  we could not fetch any rows
		if( cRowsFetched == 0 )
			return DB_S_ENDOFROWSET;

		// Simulation completed... no problems detected... we can now perform the real fetching

		// Fire events for OKTODO and ABOUTTODO after all validation has taken
		// place but before any permanent changes to the rowset state take place
		_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
		{
			// Only fire these events if we're not being called by a bookmark
			// operation (which is why m_bExternalFetch would be set to true)
			if(!m_bExternalFetch)
			{
				HRESULT hrNotify = pT->Fire_OnRowsetChange(pT,
					DBREASON_ROWSET_FETCHPOSITIONCHANGE, DBEVENTPHASE_OKTODO, FALSE);
				if (hrNotify == S_FALSE)
					return DB_E_CANCELED;
				else
				{
					hrNotify = pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
						DBEVENTPHASE_ABOUTTODO, FALSE);
					if (hrNotify == S_FALSE)
						return DB_E_CANCELED;
					else
					{
						hrNotify = pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
							DBEVENTPHASE_SYNCHAFTER, FALSE);
						if (hrNotify == S_FALSE)
							return DB_E_CANCELED;
					}
				}
			}
		}

		nCurrentRow = nFetchStartPosition; // we already calculated the 'start fetch position' in the simulation stage
		ATLASSERT( nCurrentRow >= 0 && nCurrentRow <= cRowsInSet );

		*pcRowsObtained = 0;
		CComHeapPtr<HROW> rghRowsAllocated;
		if (*prghRows == NULL)
		{
			DBROWOFFSET cHandlesToAlloc = cRowsFetched;

			rghRowsAllocated.Allocate(cHandlesToAlloc);
			if(rghRowsAllocated == NULL)
				return E_OUTOFMEMORY;

			*prghRows = rghRowsAllocated;
		}

		// now fetch the rows
		cRowsToFetch = cRows;

		while( cRowsToFetch > 0 && nCurrentRow >= 0 && nCurrentRow <= cRowsInSet )
		{
			if( ( iStepSize == 1 && nCurrentRow == cRowsInSet ) ||
				( iStepSize == -1 && nCurrentRow == 0 ) )
				break;

			DBROWOFFSET lRow = nCurrentRow;

			if( iStepSize > 0 )
			{
				while(true)
				{
					RowClass* pRow = NULL;
					typename RowClass::KeyType key = lRow + 1;
					bool bFound = m_rgRowHandles.Lookup(key,pRow);
					if( bFound && pRow != NULL )
					{
						if( pRow->m_status == DBPENDINGSTATUS_DELETED )
						{
							lRow++;
							ATLASSERT( lRow < cRowsInSet );
							continue;
						}
					}
					break;
				}
			}
			else
			{
				while(true)
				{
					lRow--;
					RowClass* pRow = NULL;
					typename RowClass::KeyType key = lRow + 1;
					bool bFound = m_rgRowHandles.Lookup(key,pRow);
					if( bFound && pRow != NULL )
					{
						if( pRow->m_status == DBPENDINGSTATUS_DELETED )
						{
							ATLASSERT( lRow >= 0 );
							continue;
						}
					}
					break;
				}
			}

			ATLASSERT( lRow >= 0 && lRow < cRowsInSet );

			hr = pT->CreateRow(lRow, *pcRowsObtained, *prghRows);

			if (FAILED(hr))
			{
				RefRows(*pcRowsObtained, *prghRows, NULL, NULL, FALSE);
				for (ULONG iRowDel = 0; iRowDel < *pcRowsObtained; iRowDel++)
					*prghRows[iRowDel] = NULL;
				*pcRowsObtained = 0;
				return hr;
			}

			_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
			{
				if (!m_bExternalFetch)
					pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
						DBEVENTPHASE_DIDEVENT, TRUE);
			}

			cRowsToFetch--;
			if( iStepSize > 0 )
				nCurrentRow = lRow + iStepSize;
			else
				nCurrentRow = lRow;
		} // while

		// If we have multiple rows fetched, return one event, per the specification
		// containing all rows activated.
		if (*pcRowsObtained >= 1)
		{
			_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
			{
				CAtlArray<HROW> rgActivated;
				for (size_t ulActivated = 0; ulActivated < *pcRowsObtained; ulActivated++)
				{
					// This is a bit of an assumption that all newly activated
					// rows would have the ref count as 1.  Another way to solve this
					// problem would be to modify the signature of CreateRow to take
					// a CAtlArray<HROW> as a parameter and store the activated rows.
					RowClass* pActiveRow;
					if( m_rgRowHandles.Lookup((*prghRows)[ulActivated], pActiveRow ) &&
						(pActiveRow != NULL && pActiveRow->m_dwRef == 1) )
					{
						_ATLTRY
						{
							rgActivated.Add((*prghRows)[ulActivated]);
						}
						_ATLCATCH( e )
						{
							_ATLDELETEEXCEPTION( e );
							return E_OUTOFMEMORY;
						}
					}
				}
				if (rgActivated.GetCount() > 0)
				{
					pT->Fire_OnRowChange(pT, (DBCOUNTITEM)rgActivated.GetCount(), rgActivated.GetData(),
						DBREASON_ROW_ACTIVATE, DBEVENTPHASE_DIDEVENT, FALSE);
				}
			}
		}

		m_iRowset = nCurrentRow;
		if( *pcRowsObtained < (DBCOUNTITEM)cRows ) // we could not fetch the requested # of rows
			hr = DB_S_ENDOFROWSET;

		if (SUCCEEDED(hr))
			rghRowsAllocated.Detach();

		return hr;
	}

	STDMETHOD(GetNextRows)(
		_In_ HCHAPTER hReserved,
		_In_ DBROWOFFSET lRowsOffset,
		_In_ DBROWCOUNT cRows,
		_Out_ DBCOUNTITEM *pcRowsObtained,
		_Outptr_result_buffer_(*pcRowsObtained) HROW **prghRows)
	{

		if( m_bRemoveDeleted && m_bIRowsetUpdate )
			return GetNextRowsSkipDeleted( hReserved, lRowsOffset, cRows, pcRowsObtained, prghRows );

		DBROWOFFSET lTmpRows = lRowsOffset;
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetImpl::GetNextRows\n"));
		T* pT = (T*) this;

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
			{
				// Note, we can't set this above this block because we may
				// inadvertantly reset somebody else's pcRowsObtained
				if (pcRowsObtained != NULL)
					*pcRowsObtained = 0;
				return DB_E_NOTREENTRANT;
			}
			else
				pT->DecrementMutex();
		}

		if (pcRowsObtained != NULL)
			*pcRowsObtained = 0;
		if (prghRows == NULL || pcRowsObtained == NULL)
			return E_INVALIDARG;
		if (cRows == 0)
			return S_OK;
		HRESULT hr = S_OK;
		typename T::ObjectLock cab(pT);
		if (lRowsOffset < 0 && !m_bCanScrollBack)
			return DB_E_CANTSCROLLBACKWARDS;
		if (cRows < 0  && !m_bCanFetchBack)
			return DB_E_CANTFETCHBACKWARDS;

		// Calculate # of rows in set and the base fetch position.  If the rowset
		// is at its head position, then lRowOffset < 0 means moving from the BACK
		// of the rowset and not the front.

		DBROWOFFSET cRowsInSet = (DBROWOFFSET)pT->m_rgRowData.GetCount();

		if (((lRowsOffset == MINLONG_PTR) && (cRowsInSet != MINLONG_PTR))
			|| AbsVal(lRowsOffset) > cRowsInSet ||
			( AbsVal(lRowsOffset) == cRowsInSet && lRowsOffset < 0 && cRows < 0 ) ||
			( AbsVal(lRowsOffset) == cRowsInSet && lRowsOffset > 0 && cRows > 0 ))
			return DB_S_ENDOFROWSET;

		// In the case where the user is moving backwards after moving forwards,
		// we do not wrap around to the end of the rowset.
		if ((m_iRowset == 0 && !m_bReset && cRows < 0) ||
			((m_iRowset + lRowsOffset) > cRowsInSet) ||
			(m_iRowset == cRowsInSet && lRowsOffset >= 0 && cRows > 0))
			return DB_S_ENDOFROWSET;

		// Fire events for OKTODO and ABOUTTODO after all validation has taken
		// place but before any permanent changes to the rowset state take place
		_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
		{
			// Only fire these events if we're not being called by a bookmark
			// operation (which is why m_bExternalFetch would be set to true)
			if(!m_bExternalFetch)
			{
				HRESULT hrNotify = pT->Fire_OnRowsetChange(pT,
					DBREASON_ROWSET_FETCHPOSITIONCHANGE, DBEVENTPHASE_OKTODO, FALSE);
				if (hrNotify == S_FALSE)
					return DB_E_CANCELED;
				else
				{
					hrNotify = pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
						DBEVENTPHASE_ABOUTTODO, FALSE);
					if (hrNotify == S_FALSE)
						return DB_E_CANCELED;
					else
					{
						hrNotify = pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
							DBEVENTPHASE_SYNCHAFTER, FALSE);
						if (hrNotify == S_FALSE)
							return DB_E_CANCELED;
					}
				}
			}
		}

		// Note, if m_bReset, m_iRowset must be 0
		if (lRowsOffset < 0 && m_bReset)
			m_iRowset = cRowsInSet;

		int iStepSize = cRows >= 0 ? 1 : -1;

		// If cRows == MINLONG_PTR, we can't use ABS on it.  Therefore, we reset it
		// to a value just greater than cRowsInSet
		if (cRows == MINLONG_PTR && cRowsInSet != MINLONG_PTR)
			cRows = cRowsInSet + 2;	// set the value to something we can deal with
		else
			cRows = AbsVal(cRows);

		if (iStepSize < 0 && m_iRowset == 0 && m_bReset && lRowsOffset <= 0)
			m_iRowset = cRowsInSet;

		lRowsOffset += m_iRowset;

		*pcRowsObtained = 0;
		CComHeapPtr<HROW> rghRowsAllocated;
		if (*prghRows == NULL)
		{
			DBROWOFFSET cHandlesToAlloc = __min(cRowsInSet, cRows);
			if (iStepSize == 1 && (cRowsInSet - lRowsOffset) < cHandlesToAlloc)
				cHandlesToAlloc = cRowsInSet - lRowsOffset;
			if (iStepSize == -1 && lRowsOffset < cHandlesToAlloc)
				cHandlesToAlloc = lRowsOffset;

			rghRowsAllocated.Allocate(cHandlesToAlloc);
			if(rghRowsAllocated == NULL)
				return E_OUTOFMEMORY;
			*prghRows = rghRowsAllocated;
		}

		while ((lRowsOffset >= 0 && cRows != 0) &&
			((lRowsOffset < cRowsInSet) || (lRowsOffset <= cRowsInSet && iStepSize < 0)))
		{
			// cRows > cRowsInSet && iStepSize < 0
			if (lRowsOffset == 0 && cRows > 0 && iStepSize < 0)
				break;

			// in the case where we have iStepSize < 0, move the row back
			// further because we want the previous row
			DBROWOFFSET lRow = lRowsOffset;
			if ((lRowsOffset == 0) && (lTmpRows == 0) && (iStepSize < 0))
				lRow = cRowsInSet;

			if (iStepSize < 0)
				lRow += iStepSize;

			hr = pT->CreateRow(lRow, *pcRowsObtained, *prghRows);

			if (FAILED(hr))
			{
				RefRows(*pcRowsObtained, *prghRows, NULL, NULL, FALSE);
				for (ULONG iRowDel = 0; iRowDel < *pcRowsObtained; iRowDel++)
					*prghRows[iRowDel] = NULL;
				*pcRowsObtained = 0;
				return hr;
			}

			_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
			{
				if (!m_bExternalFetch)
					pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
						DBEVENTPHASE_DIDEVENT, TRUE);
			}

			cRows--;
			lRowsOffset += iStepSize;
		}

		// If we have multiple rows fetched, return one event, per the specification
		// containing all rows activated.
		if (*pcRowsObtained >= 1)
		{
			_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
			{
				CAtlArray<HROW> rgActivated;
				for (size_t ulActivated = 0; ulActivated < *pcRowsObtained; ulActivated++)
				{
					// This is a bit of an assumption that all newly activated
					// rows would have the ref count as 1.  Another way to solve this
					// problem would be to modify the signature of CreateRow to take
					// a CAtlArray<HROW> as a parameter and store the activated rows.
					RowClass* pActiveRow;
					if( m_rgRowHandles.Lookup((*prghRows)[ulActivated], pActiveRow ) &&
						(pActiveRow != NULL && pActiveRow->m_dwRef == 1) )
					{
						_ATLTRY
						{
							rgActivated.Add((*prghRows)[ulActivated]);
						}
						_ATLCATCH( e )
						{
							_ATLDELETEEXCEPTION( e );
							return E_OUTOFMEMORY;
						}
					}
				}
				if (rgActivated.GetCount() > 0)
				{
					pT->Fire_OnRowChange(pT, (DBCOUNTITEM)rgActivated.GetCount(), rgActivated.GetData(),
						DBREASON_ROW_ACTIVATE, DBEVENTPHASE_DIDEVENT, FALSE);
				}
			}
		}

		m_iRowset = lRowsOffset;
		if ((lRowsOffset >= cRowsInSet && cRows) || (lRowsOffset < 0 && cRows)  ||
			(lRowsOffset == 0 && cRows > 0 && iStepSize < 0))
			hr = DB_S_ENDOFROWSET;

		if (SUCCEEDED(hr))
			rghRowsAllocated.Detach();
		return hr;
	}

	STDMETHOD(ReleaseRows)(
		_In_ DBCOUNTITEM cRows,
		_In_reads_(cRows) const HROW rghRows[],
		_In_opt_ DBROWOPTIONS rgRowOptions[],
		_Out_writes_opt_(cRows) DBREFCOUNT rgRefCounts[],
		_Out_writes_opt_(cRows) DBROWSTATUS rgRowStatus[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetImpl::ReleaseRows\n"));

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			T* pT = (T*) this;

			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();
		}

		if (cRows == 0)
			return S_OK;
		UNREFERENCED_PARAMETER(rgRowOptions);
		return RefRows(cRows, rghRows, rgRefCounts, rgRowStatus, FALSE);
	}

	STDMETHOD(RestartPosition)(_In_ HCHAPTER /*hReserved*/)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetImpl::RestartPosition\n"));

		T* pT = (T*) this;
		bool bNeedEvents = ((m_iRowset != 0 || !m_bReset));

		_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();


			// Only fire the events iff. we are actually causing a reset
			if (bNeedEvents)
			{
				HRESULT hrNotify = pT->Fire_OnRowsetChange(pT,
					DBREASON_ROWSET_FETCHPOSITIONCHANGE, DBEVENTPHASE_OKTODO, FALSE);
				if (hrNotify == S_FALSE)
					return DB_E_CANCELED;
				else
				{
					hrNotify = pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
						DBEVENTPHASE_ABOUTTODO, FALSE);
					if (hrNotify == S_FALSE)
						return DB_E_CANCELED;
					else
					{
						hrNotify = pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
							DBEVENTPHASE_SYNCHAFTER, FALSE);
						if (hrNotify == S_FALSE)
							return DB_E_CANCELED;
					}
				}

			}
		}

		// Check to see if DBPROP_CANHOLDROWS is set to false.  In this case,
		// return a DB_E_ROWSNOTRELEASED.
		CComVariant varHoldRows;
		HRESULT hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_CANHOLDROWS,
			&varHoldRows);

		if (FAILED(hr) || varHoldRows.boolVal == ATL_VARIANT_FALSE)
		{
			if (m_rgRowHandles.GetCount() > 0)
			{
				RowClass* pRow = NULL;
				POSITION pos = pT->m_rgRowHandles.GetStartPosition();

				while (pos != NULL)
				{
					typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext(pos);
					ATLENSURE_RETURN( pPair != NULL );
					HROW hRow = pPair->m_key;
					bool bFound = pT->m_rgRowHandles.Lookup(hRow, pRow);

					if (bFound && pRow != NULL &&
						pRow->m_status != DBPENDINGSTATUS_UNCHANGED)
					{
						_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
						{
							if (bNeedEvents)
							{
								pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
										DBEVENTPHASE_FAILEDTODO, TRUE);
							}
						}

						return DB_E_ROWSNOTRELEASED;
					}
				}
			}
		}

		m_iRowset = 0;
		m_bReset = true;
		_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
		{
			// listener must comply so blow off ret val.
			if (bNeedEvents)
				pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_FETCHPOSITIONCHANGE,
						DBEVENTPHASE_DIDEVENT, TRUE);
		}
		return S_OK;
	}

	MapClass  m_rgRowHandles;
	DBROWOFFSET m_iRowset; // cursor
	unsigned  m_bCanScrollBack:1;
	unsigned  m_bCanFetchBack:1;
	unsigned  m_bRemoveDeleted:1; // DBPROP_REMOVEDELETED
	unsigned  m_bIRowsetUpdate:1; // DBPROP_IRowsetUpdate
	unsigned  m_bReset:1;
	unsigned  m_bExternalFetch:1;
};


template <class T, class RowsetInterface,
		  class RowClass = CSimpleRow,
		  class MapClass = CAtlMap < RowClass::KeyType, RowClass* >,
		  class BookmarkKeyType = LONG, class BookmarkType = LONG,
		  class BookmarkMapClass = CAtlMap < RowClass::KeyType, RowClass* > >
class ATL_NO_VTABLE IRowsetLocateImpl :
	public IRowsetImpl<T, RowsetInterface, RowClass, MapClass>
{
public:
	STDMETHOD (Compare)(
		_In_ HCHAPTER /*hReserved*/,
		_In_ DBBKMARK cbBookmark1,
		_In_ const BYTE * pBookmark1,
		_In_ DBBKMARK cbBookmark2,
		_In_ const BYTE * pBookmark2,
		_Out_ DBCOMPARE * pComparison)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetLocateImpl::Compare\n"));

		T* pT = (T*)this;

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();
		}


		// Validate input parameters
		if (pComparison == NULL)
			return E_INVALIDARG;

		// ValidateBookmark catches the cbBookmark == 0 and the
		//		pBookmark == NULL -- E_INVALIDARG cases
		HRESULT hr = ValidateBookmark(cbBookmark1, pBookmark1);
		if (hr != S_OK)
			return hr;

		hr = ValidateBookmark(cbBookmark2, pBookmark2);
		if (hr != S_OK)
			return hr;

		// Return the value based on the bookmark values.  Be sure that
		// they are not specials in which case go below
		if ((*pBookmark1 == *pBookmark2) && (cbBookmark1 != 1) && (cbBookmark2 != 1))
		{
			*pComparison = DBCOMPARE_EQ;
			return S_OK;
		}

		// Determine if we are using ordered or unorder bookmarks
		CComVariant varOrdered;
		HRESULT hrProps = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_ORDEREDBOOKMARKS,
			&varOrdered);

		if ((*pBookmark1 == DBBMK_FIRST && cbBookmark1 == 1) ||
			(*pBookmark1 == DBBMK_LAST  && cbBookmark1 == 1) ||
			(*pBookmark2 == DBBMK_FIRST && cbBookmark2 == 1) ||
			(*pBookmark2 == DBBMK_LAST  && cbBookmark2 == 1) ||
			(FAILED(hrProps)) ||
			(varOrdered.boolVal == ATL_VARIANT_FALSE))
		{
			// If the bookmarks are 'specials' DBBMK_FIRST or DBBMK_LAST or the
			// bookmarks are unordered, then we return NE instead of GT or LT.
			if (*pBookmark1 == *pBookmark2)
				*pComparison = DBCOMPARE_EQ;
			else
				*pComparison = DBCOMPARE_NE;
		}
		else
		{
			// We have valid bookmarks which are not special values.
			// Since we assume that bookmarks are ordered, we'll return
			// the
			if (*(DBBKMARK*)pBookmark1 < *(DBBKMARK*)pBookmark2)
				*pComparison = DBCOMPARE_LT;
			else if (*(DBBKMARK*)pBookmark1 > *(DBBKMARK*)pBookmark2)
				*pComparison = DBCOMPARE_GT;
			else
				*pComparison = DBCOMPARE_EQ;
		}

		return S_OK;
	}

	STDMETHOD (GetRowsAt)(
		_In_ HWATCHREGION /*hReserved1*/,
		_In_ HCHAPTER hReserved2,
		_In_ DBBKMARK cbBookmark,
		_In_ const BYTE* pBookmark,
		_In_ DBROWOFFSET lRowsOffset,
		_In_ DBROWCOUNT cRows,
		_Out_ DBCOUNTITEM* pcRowsObtained,
		_Outptr_ HROW** prghRows)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetLocateImpl::GetRowsAt\n"));

		T* pT = (T*)this;

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();
		}

		typename T::ObjectLock cab(pT);

		// Check input parameters
		if (pcRowsObtained == NULL)
			return E_INVALIDARG;

		*pcRowsObtained = 0;

		if (cbBookmark == 0 || pBookmark == NULL || prghRows == NULL)
			return E_INVALIDARG;

		// Check bookmark
		HRESULT hr = ValidateBookmark(cbBookmark, pBookmark);
		if (hr != S_OK)
			return hr;

		// Set the current row position to the bookmark.  Handle any
		// normal values

		// We need to handle the offset as the start position is defined
		// as the bookmark + offset.  If the offset is negative, and we
		// do not have m_bCanScrollBack then return an error.  The
		// GetNextRows function handles the case where cRows is negative
		// and we don't have m_bCanFetchBack set.
		if (lRowsOffset < 0 && !this->m_bCanScrollBack)
			return DB_E_CANTSCROLLBACKWARDS;

		DBROWOFFSET iRowsetTemp = this->m_iRowset; // Cache the current rowset

		// Determine if this row is deleted or not.
		size_t lBookmarkIndex = (size_t)(*pBookmark);

		// -1 is uniform value for a deleted bookmark
		if( cbBookmark != 1 )
		{
			if (m_rgBookmarks[lBookmarkIndex] == -1)
			{
				this->m_iRowset = iRowsetTemp;
				return DB_E_BADBOOKMARK;
			}
			else
			{
				this->m_iRowset = m_rgBookmarks[lBookmarkIndex];
			}
		}

		if ((cbBookmark == 1) && (*pBookmark == DBBMK_FIRST))
			this->m_iRowset = 1;

		if ((cbBookmark == 1) && (*pBookmark == DBBMK_LAST))
			this->m_iRowset = (DBROWOFFSET)pT->m_rgRowData.GetCount();

		// Set the start position to m_iRowset + lRowsOffset
		this->m_iRowset += lRowsOffset;

		if (cRows >= 0)
			this->m_iRowset -=1;

		// BUG: If we get DBBMK_FIRST and lRowsOffset == -1, then we set
		// m_iRowset to 0xFFFFFFFF.

		if (this->m_iRowset < 0 || this->m_iRowset > (DBROWOFFSET)pT->m_rgRowData.GetCount())
		{
			this->m_iRowset = iRowsetTemp;
			return DB_S_ENDOFROWSET;
		}

		// Call IRowsetImpl::GetNextRows to actually get the rows.
		this->m_bExternalFetch = true;
		hr = this->GetNextRows(hReserved2, 0, cRows, pcRowsObtained, prghRows);
		this->m_bExternalFetch = false;

		// If we have multiple rows fetched, return one event, per the specification
		// containing all rows activated.
		if (*pcRowsObtained >= 1)
		{
			_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
			{
				CAtlArray<HROW> rgActivated;
				for (ULONG ulActivated = 0; ulActivated < *pcRowsObtained; ulActivated++)
				{
					// This is a bit of an assumption that all newly activated
					// rows would have the ref count as 1.  Another way to solve this
					// problem would be to modify the signature of CreateRow to take
					// a CAtlArray<HROW> as a parameter and store the activated rows.
					RowClass* pActiveRow;
					bool bFound = this->m_rgRowHandles.Lookup((*prghRows)[ulActivated], pActiveRow);
					if ( bFound && pActiveRow != NULL && pActiveRow->m_dwRef == 1)
					{
						_ATLTRY
						{
							rgActivated.Add((*prghRows)[ulActivated]);
						}
						_ATLCATCH( e )
						{
							_ATLDELETEEXCEPTION( e );
							return E_OUTOFMEMORY;
						}
					}
				}
				if (rgActivated.GetCount() > 0)
				{
					pT->Fire_OnRowChange(pT, (DBCOUNTITEM)rgActivated.GetCount(), rgActivated.GetData(),
						DBREASON_ROW_ACTIVATE, DBEVENTPHASE_DIDEVENT, FALSE);
				}
			}
		}

		this->m_iRowset = iRowsetTemp;
		return hr;
	}

	STDMETHOD (GetRowsByBookmark)(
		_In_ HCHAPTER /*hReserved*/,
		_In_ DBCOUNTITEM cRows,
		_In_reads_(cRows) const DBBKMARK rgcbBookmarks[],
		_In_reads_(cRows) const BYTE* rgpBookmarks[],
		_Out_writes_(cRows) HROW rghRows[],
		_Out_writes_opt_(cRows) DBROWSTATUS rgRowStatus[])
	{
		HRESULT hr = S_OK;
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetLocateImpl::GetRowsByBookmark\n"));
		bool bErrors = false;
		bool bSuccess = false;

		T* pT = (T*)this;

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();
		}

		typename T::ObjectLock cab(pT);
		if (rgcbBookmarks == NULL || rgpBookmarks == NULL || rghRows == NULL)
			return E_INVALIDARG;

		if (cRows == 0)
			return S_OK;	// No rows fetched in this case.

		memset(rghRows, 0, sizeof(HROW)*cRows);
		for (size_t l=0; l<cRows; l++)
		{
			// Validate each bookmark before fetching the row.  Note, it is
			// an error for the bookmark to be one of the standard values
			const BYTE* pBookmark = rgpBookmarks[l];
			hr = ValidateBookmark(rgcbBookmarks[l], pBookmark);
			if ((hr != S_OK) ||
				(*pBookmark == DBBMK_FIRST && rgcbBookmarks[l] == 1) ||
				(*pBookmark == DBBMK_LAST && rgcbBookmarks[l] == 1))
			{
				bErrors |= true;
				if (rgRowStatus != NULL)
				{
					rgRowStatus[l] = DBROWSTATUS_E_INVALID;
					continue;
				}
			}

			// Determine if this row is deleted or not by first looking in our cache
			DBROWCOUNT lBookmarkIndex = *(DBROWCOUNT*)pBookmark;
			if (m_rgBookmarks[lBookmarkIndex] == -1) // Uniform value for invalid row
			{
				if (rgRowStatus != NULL)
					rgRowStatus[l] = DBROWSTATUS_E_INVALID;

				bErrors |= true;
				continue;
			}

			// Fetch the row, we now that it is a valid row after validation.
			DBCOUNTITEM ulRowsObtained = 0;
			DBROWCOUNT lRow = m_rgBookmarks[lBookmarkIndex] - 1;
//			if (((long)*rgpBookmarks[l]) != 0)
//				lRow = ((long)*rgpBookmarks[l]) - 1;

			// Attempt to create the row
			if (this->CreateRow(lRow, ulRowsObtained, &rghRows[l]) != S_OK)
			{
				bErrors |= true;
			}
			else
			{
				if (rgRowStatus != NULL)
					rgRowStatus[l] = DBROWSTATUS_S_OK;

				bSuccess |= true;
			}
		}

		_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
		{
			CAtlArray<HROW> rgActivated;
			for (size_t ulActivated = 0; ulActivated < cRows; ulActivated++)
			{
				// This is a bit of an assumption that all newly activated
				// rows would have the ref count as 1.  Another way to solve this
				// problem would be to modify the signature of CreateRow to take
				// a CAtlArray<HROW> as a parameter and store the activated rows.
				RowClass* pActiveRow;
				bool bFound = this->m_rgRowHandles.Lookup(rghRows[ulActivated], pActiveRow);
				if (bFound && pActiveRow != NULL && pActiveRow->m_dwRef == 1)
				{
					_ATLTRY
					{
						rgActivated.Add(rghRows[ulActivated]);
					}
					_ATLCATCH( e )
					{
						_ATLDELETEEXCEPTION( e );
						return E_OUTOFMEMORY;
					}
				}
			}
			if (rgActivated.GetCount() > 0)
			{
				pT->Fire_OnRowChange(pT, (DBCOUNTITEM)rgActivated.GetCount(), rgActivated.GetData(),
					DBREASON_ROW_ACTIVATE, DBEVENTPHASE_DIDEVENT, FALSE);
			}
		}

		if (bErrors)
			return (bSuccess != false) ? DB_S_ERRORSOCCURRED : DB_E_ERRORSOCCURRED;
		else
			return hr;
	}

	STDMETHOD (Hash)(
		_In_ HCHAPTER /*hReserved*/,
		_In_ DBBKMARK cBookmarks,
		_In_reads_(cBookmarks) const DBBKMARK rgcbBookmarks[],
		_In_reads_(cBookmarks) const BYTE* rgpBookmarks[],
		_Out_writes_(cBookmarks) DBHASHVALUE rgHashedValues[],
		_Out_writes_opt_(cBookmarks) DBROWSTATUS rgBookmarkStatus[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetLocateImpl::Hash\n"));

		bool bSuccess = false;
		bool bErrors = false;

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			T* pT = (T*)this;
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();
		}

		HRESULT hr = E_FAIL;
		// validate parameters
		if (cBookmarks != 0 && (rgcbBookmarks == NULL || rgpBookmarks == NULL ||
			rgHashedValues == NULL))
			return E_INVALIDARG;

		// hash values
		for (size_t i=0; i<cBookmarks; i++)
		{
			// Check the bookmarks
			const BYTE* pBookmark = rgpBookmarks[i];
			hr = ValidateBookmark(rgcbBookmarks[i], pBookmark);
			if (FAILED(hr) ||
				(*pBookmark == DBBMK_LAST && rgcbBookmarks[i] == 1) ||
				(*pBookmark == DBBMK_FIRST && rgcbBookmarks[i] == 1))
			{
				if (rgBookmarkStatus != NULL)
					rgBookmarkStatus[i] = DBROWSTATUS_E_INVALID;
				bErrors |= true;
				continue;
			}

			rgHashedValues[i] = (DBHASHVALUE)(*(rgpBookmarks[i]));

			if (rgBookmarkStatus != NULL)
				rgBookmarkStatus[i] = DBROWSTATUS_S_OK;
			bSuccess |= true;

		}

		if (bErrors)
			return (bSuccess != false) ? DB_S_ERRORSOCCURRED : DB_E_ERRORSOCCURRED;
		else
			return S_OK;
	}

	// Data members
	CAtlArray<DBROWCOUNT> m_rgBookmarks;

	// Implementation
	protected:
	HRESULT ValidateBookmark(
		_In_ DBBKMARK cbBookmark,
		_In_ const BYTE* pBookmark)
	{
		T* pT = (T*)this;
		if (cbBookmark == 0 || pBookmark == NULL)
			return E_INVALIDARG;

		// All of our bookmarks are DBBKMARKs, if they are anything other than
		// sizeof(DBBKMARK) then we have an invalid bookmark
		if ((cbBookmark != sizeof(DBBKMARK)) && (cbBookmark != 1))
		{
			ATLTRACE(atlTraceDBProvider, 2, _T("Bookmarks are invalid length, should be DBBKMARKs"));
			return DB_E_BADBOOKMARK;
		}

		// If our bookmark is a special (i.e. cbBookmark == 1), then it should
		// be one of the accepted values.
		if ((cbBookmark == 1) && (*pBookmark != DBBMK_FIRST && *pBookmark != DBBMK_LAST))
		{
			ATLTRACE(atlTraceDBProvider, 2, _T("Bookmark is invalid"));
			return DB_E_BADBOOKMARK;
		}

		// If the contents of our bookmarks are less than 0 or greater than
		// rowcount, then they are invalid
		DBCOUNTITEM nRows = (ULONG_PTR)pT->m_rgBookmarks.GetCount();
		DBROWOFFSET lBookmarkIndex = (DBROWOFFSET)(*pBookmark);
		if (lBookmarkIndex <= -1 || lBookmarkIndex > (DBROWOFFSET)nRows )
		{
			ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetLocateImpl::Bookmark has invalid range"));
			return DB_E_BADBOOKMARK;
		}

		return S_OK;
	}
};

///////////////////////////////////////////////////////////////////////////
// IRowsetIdentityImpl

/* Function detection for InsertRow. Signature is:

STDMETHOD(InsertRow)(
_In_ HCHAPTER hReserved,
_In_ HACCESSOR hAccessor,
_In_opt_ void* pData,
_Out_opt_ HROW* phRow)
 */
template<class T, class = void>
struct Has_InsertRow : ::std::false_type {};

template<class T>
struct Has_InsertRow<T, ::std::void_t<
	decltype(::std::declval<T>().InsertRow(
		HCHAPTER{},
		HACCESSOR{},
		::std::declval<void*>(),
		::std::declval<HROW*>()))>> : ::std::true_type {};

template <class T, class RowClass = CSimpleRow>
class ATL_NO_VTABLE IRowsetIdentityImpl :
	public IRowsetIdentity
{
public:
	STDMETHOD(IsSameRow)(
		_In_ HROW hThisRow,
		_In_ HROW hThatRow)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetIdentityImpl::IsSameRow\n"));
		T* pT = (T*)this;

		// Validate row handles
		RowClass* pRow1;
		if( ! pT->m_rgRowHandles.Lookup((typename RowClass::KeyType)hThisRow, pRow1) )
			return DB_E_BADROWHANDLE;

		RowClass* pRow2;
		if( ! pT->m_rgRowHandles.Lookup((typename RowClass::KeyType)hThatRow, pRow2) )
			return DB_E_BADROWHANDLE;

		if (pRow1->m_status == DBPENDINGSTATUS_DELETED ||
			pRow2->m_status == DBPENDINGSTATUS_DELETED)
			return DB_E_DELETEDROW;

		HRESULT hr = pRow1->Compare(pRow2);

#ifndef _ATL_IF_EXISTS_FALLBACK
if constexpr(!Has_InsertRow<T>::value)
#else
__if_not_exists(T::InsertRow)
#endif
{
		return hr;
}

#ifndef _ATL_IF_EXISTS_FALLBACK
if constexpr(Has_InsertRow<T>::value)
#else
__if_exists(T::InsertRow)
#endif
{
		if (hr != S_OK)
			return hr;

		// Add one more test for providers who support IRowsetChange or Update.
		// It is possible for a series of inserts & deletes to occur to make
		// CSimpleRow appear as if it were equivalent (when it is not).  To
		// fix this, we've added an m_iOriginalRowset variable that never gets
		// changed.
		return (pRow1->m_iOriginalRowset == pRow2->m_iOriginalRowset) ? S_OK : S_FALSE;
}
	};
};

template <class T>
class ATL_NO_VTABLE IInternalConnectionImpl :
	public IInternalConnection
{
public:
	STDMETHOD(AddConnection)()
	{
		T* pT = (T*)this;
		T::_ThreadModel::Increment(&pT->m_cSessionsOpen);
		return S_OK;
	}
	STDMETHOD(ReleaseConnection)()
	{
		T* pT = (T*)this;
		T::_ThreadModel::Decrement(&pT->m_cSessionsOpen);
		return S_OK;
	}
};

template <class T>
class ATL_NO_VTABLE IInternalCommandConnectionImpl :
	public IInternalConnection
{
public:
	STDMETHOD(AddConnection)()
	{
		T* pT = (T*)this;
		T::_ThreadModel::Increment(&pT->m_cRowsetsOpen);
		return S_OK;
	}
	STDMETHOD(ReleaseConnection)()
	{
		T* pT = (T*)this;
		T::_ThreadModel::Decrement(&pT->m_cRowsetsOpen);
		return S_OK;
	}
};

template <class T>
class ATL_NO_VTABLE IObjectWithSiteSessionImpl :
	public IObjectWithSiteImpl< T >
{
public:

	virtual ~IObjectWithSiteSessionImpl()
	{
		CComPtr<IInternalConnection> pConn;
		if (this->m_spUnkSite != NULL)
		{
			if (SUCCEEDED(this->m_spUnkSite->QueryInterface(__uuidof(IInternalConnection), (void**)&pConn)))
				pConn->ReleaseConnection();
		}
	}
	STDMETHOD(SetSite)(_Inout_opt_ IUnknown* pCreator)
	{
		HRESULT hr = S_OK;
		T* pT = (T*)this;
		pT->Lock();
		this->m_spUnkSite = pCreator;
		pT->Unlock();
		CComPtr<IInternalConnection> pConn;
		if (pCreator != NULL)
		{
			hr = pCreator->QueryInterface(__uuidof(IInternalConnection), (void**)&pConn);
			if (SUCCEEDED(hr))
				hr = pConn->AddConnection();
		}
		return hr;
	}

	LONG m_cSessionsOpen;
};

template <class T>
class ATL_NO_VTABLE IRowsetCreatorImpl :
	public IObjectWithSiteImpl< T >
{
public:

	virtual ~IRowsetCreatorImpl()
	{
		CComPtr<IInternalConnection> pConn;
		if (this->m_spUnkSite != NULL)
		{
			if (SUCCEEDED(this->m_spUnkSite->QueryInterface(__uuidof(IInternalConnection), (void**)&pConn)))
				pConn->ReleaseConnection();
		}
	}

	STDMETHOD(SetSite)(_Inout_opt_ IUnknown* pCreator)
	{
		T* pT = (T*)this;
		HRESULT hr = S_OK;
		pT->Lock();
		this->m_spUnkSite = pCreator;
		pT->Unlock();
		CComVariant varPropScroll, varPropFetch, varPropRemove, varPropUpdate;
		HRESULT hrProps = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_CANSCROLLBACKWARDS, &varPropScroll);
		if (SUCCEEDED(hrProps))
			pT->m_bCanScrollBack = (varPropScroll.boolVal != ATL_VARIANT_FALSE);
		hrProps = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_CANFETCHBACKWARDS, &varPropFetch);
		if (SUCCEEDED(hrProps))
			pT->m_bCanFetchBack = (varPropFetch.boolVal != ATL_VARIANT_FALSE);
		hrProps = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_REMOVEDELETED, &varPropRemove);
		if (SUCCEEDED(hrProps))
			pT->m_bRemoveDeleted = (varPropRemove.boolVal != ATL_VARIANT_FALSE);
		hrProps = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_IRowsetUpdate, &varPropUpdate);
		if (SUCCEEDED(hrProps))
			pT->m_bIRowsetUpdate = (varPropUpdate.boolVal != ATL_VARIANT_FALSE);
		CComPtr<IInternalConnection> pConn;
		if (pCreator != NULL)
		{
			if (SUCCEEDED(pCreator->QueryInterface(__uuidof(IInternalConnection), (void**)&pConn)))
				hr = pConn->AddConnection();
		}
		return hr;
	}

	LONG m_cRowsetsOpen;
};

// IRowsetInfoImpl
template <class T, class PropClass = T>
class ATL_NO_VTABLE IRowsetInfoImpl :
	public IRowsetInfo,
	public CUtlProps<PropClass>
{
public:
	static UPROPSET* _GetPropSet(
		_Out_opt_ ULONG* pNumPropSets,
		_Out_ ULONG* pcElemPerSupported,
		_Inout_opt_ UPROPSET* pSet = NULL,
		_Out_opt_ GUID* pguidSet = NULL)
	{
		//protects from recurrence call this method (compiler warning C4717)
		ATLASSERT(&IRowsetInfoImpl<T>::_GetPropSet != &PropClass::_GetPropSet);

		if (&IRowsetInfoImpl<T>::_GetPropSet != &PropClass::_GetPropSet)
		{
			return PropClass::_GetPropSet(pNumPropSets, pcElemPerSupported, pSet, pguidSet);
		}
		return NULL;
	}
	STDMETHOD(GetProperties)(
		_In_ const ULONG cPropertyIDSets,
		_In_reads_(cPropertyIDSets) const DBPROPIDSET rgPropertyIDSets[],
		_Out_ ULONG *pcPropertySets,
		_Outptr_result_buffer_maybenull_(*pcPropertySets) DBPROPSET **prgPropertySets)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetInfoImpl::GetProperties\n"));

		// IRowsetInfo can't return PROPERTIESINERROR.  Therefore, disable
		// checking for it.  Instead, treat it as any normal property set.
		this->m_dwFlags &= ~ARGCHK_PROPERTIESINERROR;
		HRESULT hr = this->GetPropertiesArgChk(cPropertyIDSets, rgPropertyIDSets,
			pcPropertySets, prgPropertySets);
		if(SUCCEEDED(hr))
		{
			// Scan property sets to allow user defined properties
			ULONG ulPropSets = 0;
			ULONG ulPropElems = 0;
			ULONG ulPropInits = 0;
			UPROPSET* pSetA = NULL;
			UPROPSET* pSetTemp = NULL;
			ULONG l=0;
			ULONG cSets = (ULONG)(ULONG_PTR)T::_GetPropSet(NULL, &ulPropElems);

			CTempBuffer<UPROPSET> tmpBuffer;
			pSetA = tmpBuffer.Allocate(cSets);
			if (pSetA == NULL)
				return E_OUTOFMEMORY;  // We shouldn't get this but...
			pSetTemp = T::_GetPropSet(&ulPropSets, &ulPropElems, pSetA);

			typedef const GUID* PCGUID;

			for(l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ulPropInits++;
			}

			CTempBuffer<PCGUID> tmpBuffer2;
			PCGUID* ppGuid = tmpBuffer2.Allocate(ulPropInits);
			if (ppGuid == NULL)
				return E_OUTOFMEMORY;
			ulPropInits = 0;
			for (l=0; l<cSets; l++)
			{
				if (pSetTemp[l].bIsChained != true)
					ppGuid[ulPropInits++] = pSetTemp[l].pPropSet;
			}

			return CUtlProps<PropClass>::GetProperties(cPropertyIDSets,
					rgPropertyIDSets, pcPropertySets, prgPropertySets,
					ulPropInits, ppGuid);
		}
		else
			return hr;
	}

	OUT_OF_LINE ATLCOLUMNINFO* InternalGetColumnInfo(_Out_ DBORDINAL* pcCols)
	{
		return T::GetColumnInfo((T*)this, pcCols);
	}
ATLPREFAST_SUPPRESS(6387)
	STDMETHOD(GetReferencedRowset)(
		_In_ DBORDINAL iOrdinal,
		_In_ REFIID riid,
		_Outptr_ IUnknown **ppReferencedRowset)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetInfoImpl::GetReferencedRowset\n"));
		DBORDINAL cCols=0;

		// Check Arguments
		if( ppReferencedRowset == NULL )
		{
			ATLTRACE(atlTraceDBProvider, 0, _T("IRowsetInfoImpl::GetReferencedRowset : Error NULL IUnk output Param\n"));
			return E_INVALIDARG;
		}
		*ppReferencedRowset = NULL;

		// Check to see if column in question is a bookmark
		ATLCOLUMNINFO* pColInfo = InternalGetColumnInfo(&cCols);
		DBORDINAL iColInfo;
		for (iColInfo = 0;
			 iColInfo < cCols && iOrdinal != pColInfo[iColInfo].iOrdinal;
			 iColInfo++);
		if (iColInfo == cCols)
			return DB_E_BADORDINAL;
		ATLCOLUMNINFO* pColCur = &(pColInfo[iColInfo]);

		if ((pColCur->dwFlags & DBCOLUMNFLAGS_ISBOOKMARK) == 0)
			return DB_E_NOTAREFERENCECOLUMN;

		// Query for requested interface
		return QueryInterface(riid, (void**)ppReferencedRowset);
	}
ATLPREFAST_UNSUPPRESS()

	STDMETHOD(GetSpecification)(
		_In_ REFIID riid,
		_Outptr_ IUnknown **ppSpecification)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetInfoImpl::GetSpecification\n"));

		if (ppSpecification == NULL)
			return E_INVALIDARG;
		T* pT = (T*) this;
		typename T::ObjectLock cab(pT);
		ATLASSERT(pT->m_spUnkSite != NULL);
		return pT->m_spUnkSite->QueryInterface(riid, (void**)ppSpecification);
	}
};


/*
template <class Storage, class ContainedArray = CAtlArray<Storage> >
class CUpdateArray :
	public ContainedArray
{
public:

	void RemoveAll()
	{
		ContainedArray::RemoveAll();
		m_rgRowLink.RemoveAll();
	}

	BOOL Add(Storage& rStorage)
	{
		if (ContainedArray::Add(rStorage))
		{
			if (!m_rgRowLink.Add(GetCount() - 1))
			{
				RemoveAt(GetCount() - 1);
				return FALSE;
			}
			return TRUE;
		}
		return FALSE;
	}

	BOOL RemoveAt(int nIndex)
	{
		if (ContainedArray::RemoveAt(m_rgRowLink[nIndex]))
		{
			// Decrement any links following the current row
			for (long l=nIndex+1; l<m_rgRowLink.GetCount(); l++)
			{
				long* pElem = &(m_rgRowLink.m_aT[l]);
				ATLASSERT(pElem != NULL);
				(*pElem)--;
			}
			return m_rgRowLink.RemoveAt(nIndex);
		}
		return FALSE;
	}

	ContainedArray::_ArrayElementType& operator[] (int nIndex)
	{
		ATLASSERT(nIndex >= 0 && nIndex < GetCount());
		return ContainedArray::operator[](m_rgRowLink[nIndex]);
	}

	const ContainedArray::_ArrayElementType& operator[] (int nIndex) const
	{
		ATLASSERT(nIndex >= 0 && nIndex < GetCount());
		return ContainedArray::operator[](m_rgRowLink[nIndex]);
	}

	CSimpleValArray<long> m_rgRowLink;
};
*/

class CComSharedMutex
{
public:
	CComSharedMutex()
	{
		m_lInterlockedVariable = -1;
	};

	inline bool IncrementMutex()
	{
		long lAvailable = ::InterlockedIncrement(&m_lInterlockedVariable);
		if (lAvailable > 0)
		{
			// Mutex is held, decrement and return false
			DecrementMutex();
			return false;
		}
		else
			return true;
	};
	inline void DecrementMutex()
	{
		::InterlockedDecrement(&m_lInterlockedVariable);
	};

	long m_lInterlockedVariable;
};


template <class T, class ReentrantEventSync = CComSharedMutex>
class IRowsetNotifyCP :
	public IConnectionPointImpl<T, &__uuidof(IRowsetNotify), CComDynamicUnkArray>,
	public ReentrantEventSync
{
public:
	HRESULT Fire_OnFieldChange(
		_Inout_ IRowset* pRowset,
		_In_ HROW hRow,
		_In_ DBORDINAL cColumns,
		_In_reads_(cColumns) DBORDINAL* rgColumns,
		_In_ DBREASON eReason,
		_In_ DBEVENTPHASE ePhase,
		_In_ BOOL fCantDeny)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetNotifyCP::Fire_OnFieldChange\n"));

		this->IncrementMutex();	// Lock the event handler so other's can't call methods
		HRESULT ret = S_OK;
		T* pT = static_cast<T*>(this);
		int nConnectionIndex;
		int nConnections = this->m_vec.GetSize();

		for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
		{
			pT->Lock();
			CComPtr<IUnknown> sp = this->m_vec.GetAt(nConnectionIndex);
			pT->Unlock();
			IRowsetNotify* pIRowsetNotify = reinterpret_cast<IRowsetNotify*>(sp.p);
			if (pIRowsetNotify != NULL)
				ret = pIRowsetNotify->OnFieldChange(pRowset, hRow, cColumns,
					rgColumns, eReason, ePhase, fCantDeny);
			switch(ePhase)
			{
			case DBEVENTPHASE_OKTODO:
				if (ret == S_FALSE  && fCantDeny == FALSE)
				{
					// if we get an S_FALSE back, a consumer has vetoed the
					// request.  In this case, we should send a FAILEDTODO
					// to ONLY those consumers already notified.
					for (int nFailedIndex = 0; nFailedIndex <= nConnectionIndex; nFailedIndex++)
					{
						pT->Lock();
						CComPtr<IUnknown> spFailed = this->m_vec.GetAt(nFailedIndex);
						pT->Unlock();
						IRowsetNotify* pIFailedNotify = reinterpret_cast<IRowsetNotify*>(spFailed.p);
						if (pIFailedNotify != NULL)
							pIFailedNotify->OnFieldChange(pRowset, hRow, cColumns, rgColumns,
								eReason, DBEVENTPHASE_FAILEDTODO, FALSE);
					}
					// Terminate the loop as no further consumers should be
					// notified.
					this->DecrementMutex();
					return ret;
				}
				else
				{
					if (ret != S_OK)
						ret = S_OK;
				}
				break;
			case DBEVENTPHASE_ABOUTTODO:
			case DBEVENTPHASE_SYNCHAFTER:
				if (ret == S_FALSE && fCantDeny == FALSE)
				{
					Fire_OnFieldChange(pRowset, hRow, cColumns, rgColumns,
						eReason, DBEVENTPHASE_FAILEDTODO, FALSE);
					this->DecrementMutex();
					return ret;
				}
				else
				{
					if (ret != S_OK)
						ret = S_OK;
				}
				break;
			case DBEVENTPHASE_FAILEDTODO:
			case DBEVENTPHASE_DIDEVENT:
				break; // Nothing todo
			default:
				ATLTRACE(atlTraceDBProvider, 0, _T("IRowsetNotifyCP::Fire_OnFieldChange: Unknown Phase requested\n"));
				ATLASSERT(FALSE);
				this->DecrementMutex();
				return E_FAIL;
			};
		}
		this->DecrementMutex();
		if( ret != S_OK && ret != S_FALSE )
			ret = S_OK;
		return ret;

	}
	HRESULT Fire_OnRowChange(
		_Inout_ IRowset* pRowset,
		_In_ DBCOUNTITEM cRows,
		_In_reads_(cRows) const HROW rghRows[],
		_In_ DBREASON eReason,
		_In_ DBEVENTPHASE ePhase,
		_In_ BOOL fCantDeny)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetNotifyCP::Fire_OnRowChange\n"));
		this->IncrementMutex();  // Lock the handler so other's can't call methods
		HRESULT ret = S_OK;
		T* pT = static_cast<T*>(this);
		int nConnectionIndex;
		int nConnections = this->m_vec.GetSize();

		for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
		{
			pT->Lock();
			CComPtr<IUnknown> sp = this->m_vec.GetAt(nConnectionIndex);
			pT->Unlock();
			IRowsetNotify* pIRowsetNotify = reinterpret_cast<IRowsetNotify*>(sp.p);
			if (pIRowsetNotify != NULL)
			{
				ret = pIRowsetNotify->OnRowChange(pRowset, cRows, rghRows, eReason, ePhase, fCantDeny);
			}
			switch(ePhase)
			{
			case DBEVENTPHASE_OKTODO:
				if (ret == S_FALSE && fCantDeny == FALSE)
				{
					// if we get an S_FALSE back, a consumer has vetoed the
					// request.  In this case, we should send a FAILEDTODO
					// to ONLY those consumers already notified.
					for (int nFailedIndex = 0; nFailedIndex <= nConnectionIndex; nFailedIndex++)
					{
						pT->Lock();
						CComPtr<IUnknown> spFailed = this->m_vec.GetAt(nFailedIndex);
						pT->Unlock();
						IRowsetNotify* pIFailedNotify = reinterpret_cast<IRowsetNotify*>(spFailed.p);
						if (pIFailedNotify != NULL)
							pIFailedNotify->OnRowChange(pRowset, cRows, rghRows, eReason,
								DBEVENTPHASE_FAILEDTODO, FALSE);
					}
					// Terminate the loop as no further consumers should be
					// notified.
					this->DecrementMutex();
					return ret;
				}
				break;
			case DBEVENTPHASE_SYNCHAFTER:
			case DBEVENTPHASE_ABOUTTODO:
				if (ret	== S_FALSE && fCantDeny == FALSE)
				{
					Fire_OnRowChange(pRowset, cRows, rghRows, eReason,
						DBEVENTPHASE_FAILEDTODO, FALSE);
					this->DecrementMutex();
					return ret;
				}
				break;
			case DBEVENTPHASE_FAILEDTODO:
			case DBEVENTPHASE_DIDEVENT:
				break; // Nothing todo
			default:
				ATLTRACE(atlTraceDBProvider, 0, _T("IRowsetNotifyCP::Fire_OnRowChange: Unknown Phase requested\n"));
				ATLASSERT(FALSE);
				this->DecrementMutex();
				return E_FAIL;
			};
		}
		this->DecrementMutex();
		if( ret != S_OK && ret != S_FALSE )
			ret = S_OK;
		return ret;

	}
	HRESULT Fire_OnRowsetChange(
		_Inout_ IRowset* pRowset,
		_In_ DBREASON eReason,
		_In_ DBEVENTPHASE ePhase,
		_In_ BOOL fCantDeny)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetNotifyCP::Fire_OnRowsetChange\n"));
		this->IncrementMutex(); // Lock the handler so others can't call methods
		HRESULT ret = S_OK;
		T* pT = static_cast<T*>(this);
		int nConnectionIndex;
		int nConnections = this->m_vec.GetSize();

		for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
		{
			pT->Lock();
			CComPtr<IUnknown> sp = this->m_vec.GetAt(nConnectionIndex);
			pT->Unlock();
			IRowsetNotify* pIRowsetNotify = reinterpret_cast<IRowsetNotify*>(sp.p);
			if (pIRowsetNotify != NULL)
				ret = pIRowsetNotify->OnRowsetChange(pRowset, eReason, ePhase, fCantDeny);
			switch(ePhase)
			{
			case DBEVENTPHASE_OKTODO:
				if (ret == S_FALSE && fCantDeny == FALSE)
				{
					// if we get an S_FALSE back, a consumer has vetoed the
					// request.  In this case, we should send a FAILEDTODO
					// to ONLY those consumers already notified.
					for (int nFailedIndex = 0; nFailedIndex <= nConnectionIndex; nFailedIndex++)
					{
						pT->Lock();
						CComPtr<IUnknown> spFailed = this->m_vec.GetAt(nFailedIndex);
						pT->Unlock();
						IRowsetNotify* pIFailedNotify = reinterpret_cast<IRowsetNotify*>(spFailed.p);
						if (pIFailedNotify != NULL)
							pIFailedNotify->OnRowsetChange(pRowset, eReason,
								DBEVENTPHASE_FAILEDTODO, FALSE);
					}
					// Terminate the loop as no further consumers should be
					// notified.
					this->DecrementMutex();
					return ret;
				}
				break;
			case DBEVENTPHASE_ABOUTTODO:
			case DBEVENTPHASE_SYNCHAFTER:
				if (ret == S_FALSE && fCantDeny == FALSE)
				{
					Fire_OnRowsetChange(pRowset, eReason, DBEVENTPHASE_FAILEDTODO,
						FALSE);
					this->DecrementMutex();
					return ret;
				}
				break;
			case DBEVENTPHASE_FAILEDTODO:
			case DBEVENTPHASE_DIDEVENT:
				break; // Nothing todo
			default:
				ATLTRACE(atlTraceDBProvider, 0, _T("IRowsetNotifyCP::Fire_OnRowChange: Unknown Phase requested\n"));
				ATLASSERT(FALSE);
				this->DecrementMutex();
				return E_FAIL;
			};
		}
		this->DecrementMutex();
		if( ret != S_OK && ret != S_FALSE )
			ret = S_OK;
		return ret;

	}
};


template <class T, class Storage,
		  class ArrayType = CAtlArray<Storage>,
		  class RowsetInterface = IRowsetImpl < T, IRowset >,
		  class RowClass = CSimpleRow >
class CRowsetBaseImpl :
	public IAccessorImpl<T>,
	public IRowsetIdentityImpl<T, RowClass>,
	public IRowsetCreatorImpl<T>,
	public IColumnsInfoImpl<T>,
	public IConvertTypeImpl<T>,
	public RowsetInterface
{
public:
	HRESULT NameFromDBID(
		_In_ DBID* pDBID,
		_Inout_ CComBSTR& bstr,
		_In_ bool bIndex)
	{

		if (pDBID->uName.pwszName != NULL)
		{
			ATLTRY( bstr = pDBID->uName.pwszName );
			if (!bstr)
				return E_OUTOFMEMORY;
			return S_OK;
		}

		return (bIndex) ? DB_E_NOINDEX : DB_E_NOTABLE;
	}

	HRESULT GetCommandFromID(
		_In_opt_ DBID* pTableID,
		_In_opt_ DBID* pIndexID)
	{
		if (pTableID == NULL && pIndexID == NULL)
			return E_INVALIDARG;

		if (pTableID != NULL && pTableID->eKind == DBKIND_NAME)
		{
			HRESULT hr = NameFromDBID(pTableID, m_strCommandText, true);
			if (FAILED(hr))
				return hr;
			if (pIndexID != NULL)
			{
				if (pIndexID->eKind == DBKIND_NAME)
				{
					hr = NameFromDBID(pIndexID, m_strIndexText, false);
					if (FAILED(hr))
					{
						m_strCommandText.Empty();
						return hr;
					}
				}
				else
				{
					m_strCommandText.Empty();
					return DB_E_NOINDEX;
				}
			}
			return S_OK;
		}
		if (pIndexID != NULL && pIndexID->eKind == DBKIND_NAME)
			return NameFromDBID(pIndexID, m_strIndexText, false);

		return S_OK;
	}

	HRESULT ValidateCommandID(
		_In_opt_ DBID* pTableID,
		_In_opt_ DBID* pIndexID)
	{
		HRESULT hr = S_OK;

		if (pTableID != NULL)
		{
			hr = CUtlProps<T>::IsValidDBID(pTableID);

			if (hr != S_OK)
				return hr;

			// Check for a NULL TABLE ID (where its a valid pointer but NULL)
			if ((pTableID->eKind == DBKIND_GUID_NAME ||
				pTableID->eKind == DBKIND_NAME ||
				pTableID->eKind == DBKIND_PGUID_NAME)
				&& pTableID->uName.pwszName == NULL)
				return DB_E_NOTABLE;
		}

		if (pIndexID != NULL)
			hr = CUtlProps<T>::IsValidDBID(pIndexID);

		return hr;
	}

	HRESULT SetCommandText(
		_In_opt_ DBID* pTableID,
		_In_opt_ DBID* pIndexID)
	{
		T* pT = (T*)this;
		HRESULT hr = pT->ValidateCommandID(pTableID, pIndexID);
		if (FAILED(hr))
			return hr;
		hr = pT->GetCommandFromID(pTableID, pIndexID);
		return hr;
	}

	_Ret_writes_(*pcCols) static ATLCOLUMNINFO* GetColumnInfo(
		_In_reads_opt_(1) T* pv,
		_Out_ DBORDINAL* pcCols)
	{
		return Storage::GetColumnInfo(pv,pcCols);
	}

	CComBSTR m_strCommandText;
	CComBSTR m_strIndexText;
	ArrayType m_rgRowData;
};


template <class T, class Storage, class CreatorClass,
		  class ArrayType = CAtlArray<Storage>,
  		  class RowClass = CSimpleRow,
		  class RowsetInterface = IRowsetImpl < T, IRowset > >
class CRowsetImpl :
	public CComObjectRootEx<typename CreatorClass::_ThreadModel>,
	public CRowsetBaseImpl<T, Storage, ArrayType, RowsetInterface, RowClass>,
	public IRowsetInfoImpl<T, typename CreatorClass::_PropClass>
{
public:
	typedef CRowsetImpl< T, Storage, CreatorClass, ArrayType, RowClass, RowsetInterface> _RowsetBaseClass;
	typedef ArrayType _RowsetArrayType;
	typedef Storage _StorageClass;
	typedef CreatorClass _RowsetCreatorClass;

BEGIN_COM_MAP(CRowsetImpl)
	COM_INTERFACE_ENTRY(IRowsetInfo)
	COM_INTERFACE_ENTRY(IAccessor)
	COM_INTERFACE_ENTRY(IObjectWithSite)
	COM_INTERFACE_ENTRY(IColumnsInfo)
	COM_INTERFACE_ENTRY(IConvertType)
	COM_INTERFACE_ENTRY(IRowsetIdentity)
	COM_INTERFACE_ENTRY(IRowset)
END_COM_MAP()

	HRESULT FinalConstruct()
	{
		HRESULT hr = IAccessorImpl<T>::FinalConstruct();
		if (FAILED(hr))
			return hr;
		return CConvertHelper::FinalConstruct();
	}

	void FinalRelease()
	{
		this->m_rgRowData.RemoveAll();
		IAccessorImpl<T>::FinalRelease();
		_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
		{
			T* pT = (T*)this;
			pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_RELEASE, DBEVENTPHASE_DIDEVENT, TRUE);
		}
	}
};


template <class T, class Storage, class CreatorClass,
		  class ArrayType = CAtlArray<Storage>,
  		  class RowClass = CSimpleRow,
		  class RowsetInterface = IRowsetImpl < T, IRowset > >
class CSchemaRowsetImpl :
	public CComObjectRootEx<typename CreatorClass::_ThreadModel>,
	public CRowsetBaseImpl<T, Storage, ArrayType, RowsetInterface, RowClass>,
	public IRowsetInfoImpl<T>
{
public:
	typedef CSchemaRowsetImpl< T, Storage, CreatorClass, ArrayType, RowClass, RowsetInterface> _RowsetBaseClass;
	typedef ArrayType _RowsetArrayType;
	typedef Storage _StorageClass;
	typedef CreatorClass _RowsetCreatorClass;

BEGIN_COM_MAP(CSchemaRowsetImpl)
	COM_INTERFACE_ENTRY(IRowsetInfo)
	COM_INTERFACE_ENTRY(IAccessor)
	COM_INTERFACE_ENTRY(IObjectWithSite)
	COM_INTERFACE_ENTRY(IColumnsInfo)
	COM_INTERFACE_ENTRY(IConvertType)
	COM_INTERFACE_ENTRY(IRowsetIdentity)
	COM_INTERFACE_ENTRY(IRowset)
END_COM_MAP()

	HRESULT FinalConstruct()
	{
		HRESULT hr = IAccessorImpl<T>::FinalConstruct();
		if (FAILED(hr))
			return hr;
		return CConvertHelper::FinalConstruct();
	}

	void FinalRelease()
	{
		this->m_rgRowData.RemoveAll();
		_ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE
		{
			T* pT = (T*)this;
			pT->Fire_OnRowsetChange(pT, DBREASON_ROWSET_RELEASE, DBEVENTPHASE_DIDEVENT, TRUE);
		}
	}
};


template <const GUID* pguidProvider>
class CErrorReporterHelper
{
public:

	HRESULT PostError(
		_In_ HRESULT hrErr,
		_In_ IID* piid)
	{
		HRESULT	hr = S_OK;
		if (piid == NULL)
			return E_INVALIDARG;

		CComPtr<ICreateErrorInfo>	spCrtErrInfo;
		CComPtr<IErrorInfo>			spIErrorInfo;
		CComPtr<IErrorRecords>		spIErrorRecords;

		hr = CreateErrorInfo(&spCrtErrInfo);
		if (SUCCEEDED(hr))
		{
			ERRORINFO		errorinfo;
			memset(&errorinfo, 0, sizeof(ERRORINFO));
			errorinfo.clsid		= *pguidProvider;
			errorinfo.dispid	= NULL;
			errorinfo.dwMinor	= 0;
			errorinfo.hrError	= hrErr;
			errorinfo.iid		= *piid;

			spCrtErrInfo->SetGUID(errorinfo.iid);
			spCrtErrInfo->SetSource(L"Provider PROGID");
			spCrtErrInfo->SetDescription(L"Error Description");
			spCrtErrInfo->SetHelpFile(L"provider.hlp");
			spCrtErrInfo->SetHelpContext(1);

			//Obtain the error object or create a new one if none exists
			if ( FAILED(GetErrorInfo(0, &spIErrorInfo)) || ( spIErrorInfo == NULL ) )
			{
				hr = spIErrorInfo.CoCreateInstance(CLSID_EXTENDEDERRORINFO);
				if( FAILED(hr))
					return hr;
			}

			// Obtain the IErrorRecord Interface
			hr = spIErrorInfo->QueryInterface(&spIErrorRecords);
			if( SUCCEEDED(hr))
			{
				hr = spIErrorRecords->AddErrorRecord(&errorinfo, 0, NULL, spCrtErrInfo, 0);
				if( FAILED(hr))
					return hr;
			}
		}

		if( spIErrorInfo != NULL)
			SetErrorInfo(0, spIErrorInfo);

		return hr;
	}
};

struct ATLERRORINFO
{
	ERRORINFO*		  pInfo;
	DISPPARAMS		  dispparams;
	CComPtr<IUnknown> spCustError;
	DWORD			  dwLookupID;

};

template <class T, class RecordClass = ATLERRORINFO>
class IErrorRecordsImpl :
	public IErrorRecords
{
public:
	STDMETHOD(AddErrorRecord)(
		_In_ ERRORINFO *pErrorInfo,
		_In_ DWORD dwLookupID,
		_In_ DISPPARAMS *pdispparams,
		_In_ IUnknown *punkCustomError,
		_In_ DWORD)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IErrorRecordsImpl::AddErrorRecord\n"));
		if (pErrorInfo == NULL)
			return E_INVALIDARG;
		ATLERRORINFO err;

		err.pInfo = pErrorInfo;
		err.dispparams = *pdispparams;
		err.spCustError = punkCustomError;
		err.dwLookupID = dwLookupID;

		//if (!m_rgErrors.Add(err))
		//	return E_OUTOFMEMORY;
		_ATLTRY
		{
			m_rgErrors.Add(err);
		}
		_ATLCATCH( e )
		{
			_ATLDELETEEXCEPTION( e );
			return E_OUTOFMEMORY;
		}

		return S_OK;
	}

	STDMETHOD(GetBasicErrorInfo)(
		_In_ ULONG ulRecordNum,
		_In_ ERRORINFO *pErrorInfo)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IErrorRecordsImpl::GetBasicErrorInfo\n"));
		if (pErrorInfo == NULL)
			return E_INVALIDARG;
		if (ulRecordNum >= m_rgErrors.GetCount())
			return DB_E_BADRECORDNUM;

		*pErrorInfo = *(m_rgErrors[ulRecordNum].pInfo);
		return S_OK;
	}

	STDMETHOD(GetCustomErrorObject)(
		_In_ ULONG ulRecordNum,
		_In_ REFIID riid,
		_Outptr_ IUnknown **ppObject)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IErrorRecordsImpl::GetCustomErrorObject\n"));
		if (ppObject == NULL)
			return E_INVALIDARG;
		if (ulRecordNum >= m_rgErrors.GetCount())
			return DB_E_BADRECORDNUM;

		CComPtr<IUnknown> spUnkCust;
		spUnkCust = m_rgErrors[ulRecordNum].spCustError;
		return spUnkCust->QueryInterface(riid, (void**)ppObject);

	}

	STDMETHOD(GetErrorInfo)(
		_In_ ULONG ulRecordNum,
		_In_ LCID,
		_Outptr_ IErrorInfo **ppErrorInfo)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IErrorRecordsImpl::GetErrorInfo\n"));

		if (ppErrorInfo == NULL)
			return E_INVALIDARG;
		if (ulRecordNum >= m_rgErrors.GetCount())
			return DB_E_BADRECORDNUM;

		CComPtr<ICreateErrorInfo> spErrorInfo;

		if (FAILED(CreateErrorInfo(&spErrorInfo)))
			return E_OUTOFMEMORY;

		ERRORINFO& rInfo = *(m_rgErrors[ulRecordNum].pInfo);
		T* pT = (T*)this;
		spErrorInfo->SetDescription(pT->GetErrorDescriptionString(rInfo));
		spErrorInfo->SetGUID(pT->GetErrorGUID(rInfo));
		spErrorInfo->SetHelpContext(pT->GetErrorHelpContext(rInfo));
		spErrorInfo->SetHelpFile(pT->GetErrorHelpFile(rInfo));
		spErrorInfo->SetSource(pT->GetErrorSource(rInfo));
		return spErrorInfo->QueryInterface(__uuidof(IErrorInfo), (void**)ppErrorInfo);
	}

	STDMETHOD(GetErrorParameters)(
		_In_ ULONG ulRecordNum,
		_Out_ DISPPARAMS *pdispparams)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IErrorRecordsImpl::GetErrorParameters\n"));
		if (pdispparams == NULL)
			return E_INVALIDARG;
		if (ulRecordNum >= m_rgErrors.GetCount())
			return DB_E_BADRECORDNUM;

		*pdispparams = m_rgErrors[ulRecordNum].dispparams;
		return S_OK;
	}

	STDMETHOD(GetRecordCount)(_Out_ ULONG *pcRecords)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IErrorRecordsImpl::GetRecordCount\n"));
		if (pcRecords == NULL)
		{
			return E_INVALIDARG;
		}
#ifdef _WIN64
		size_t cRecords = m_rgErrors.GetCount();
		if (cRecords > ULONG_MAX)
		{
			*pcRecords = 0;
			return E_FAIL;
		}
		*pcRecords = (ULONG)cRecords;
#else
		*pcRecords = m_rgErrors.GetCount();
#endif
		return S_OK;
	}

	LPOLESTR GetErrorDescriptionString(_In_ ERRORINFO&)
	{
		return OLESTR("The Error Description String");
	}

	REFGUID GetErrorGUID(_In_ ERRORINFO&)
	{
		return GUID_NULL;
	}

	DWORD GetErrorHelpContext(_In_ ERRORINFO&)
	{
		return 0;
	}

	LPOLESTR GetErrorHelpFile(_In_ ERRORINFO&)
	{
		return OLESTR("The Error Help File");
	}

	LPOLESTR GetErrorSource(_In_ ERRORINFO&)
	{
		return OLESTR("The ErrorInfo");
	}

	CAtlArray<RecordClass> m_rgErrors;
};


class CTABLESRow
{
public:

	WCHAR m_szCatalog[129];
	WCHAR m_szSchema[129];
	WCHAR m_szTable[129];
	WCHAR m_szType[129];
	WCHAR m_szDesc[129];
	GUID  m_guid;
	ULONG m_ulPropID;
	DATE m_DateCreated;
	DATE m_DateModified;

	CTABLESRow()
	{
		Checked::wcscpy_s(m_szCatalog, _countof(m_szCatalog), L"");
		Checked::wcscpy_s(m_szSchema, _countof(m_szSchema), L"");
		Checked::wcscpy_s(m_szTable, _countof(m_szTable), L"");
		Checked::wcscpy_s(m_szType, _countof(m_szType), L"");
		Checked::wcscpy_s(m_szDesc, _countof(m_szDesc), L"");
		m_guid = GUID_NULL;
		m_ulPropID = 0;
		m_DateCreated = 0.0;
		m_DateModified = 0.0;
	}

BEGIN_PROVIDER_COLUMN_MAP(CTABLESRow)
	PROVIDER_COLUMN_ENTRY_WSTR("TABLE_CATALOG", 1, m_szCatalog)
	PROVIDER_COLUMN_ENTRY_WSTR("TABLE_SCHEMA", 2, m_szSchema)
	PROVIDER_COLUMN_ENTRY_WSTR("TABLE_NAME", 3, m_szTable)
	PROVIDER_COLUMN_ENTRY_WSTR("TABLE_TYPE", 4, m_szType)
	PROVIDER_COLUMN_ENTRY_PS("TABLE_GUID", 5, 0xFF, 0xFF, m_guid)
	PROVIDER_COLUMN_ENTRY_WSTR("DESCRIPTION", 6, m_szDesc)
	PROVIDER_COLUMN_ENTRY_PS("TABLE_PROPID", 7, 10, 0xFF, m_ulPropID)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("DATE_CREATED", 8, DBTYPE_DATE, 0xFF, 0xFF, m_DateCreated)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("DATE_MODIFIED", 9, DBTYPE_DATE, 0xFF, 0xFF, m_DateModified)
END_PROVIDER_COLUMN_MAP()

};


class CCOLUMNSRow
{
public:

	WCHAR			m_szTableCatalog[129];
	WCHAR			m_szTableSchema[129];
	WCHAR			m_szTableName[129];
	WCHAR			m_szColumnName[129];
	GUID			m_guidColumn;
	ULONG			m_ulColumnPropID;
	ULONG			m_ulOrdinalPosition;
	VARIANT_BOOL	m_bColumnHasDefault;
	WCHAR			m_szColumnDefault[129];
	ULONG			m_ulColumnFlags;
	VARIANT_BOOL	m_bIsNullable;
	USHORT			m_nDataType;
	GUID			m_guidType;
	ULONG			m_ulCharMaxLength;
	ULONG			m_ulCharOctetLength;
	USHORT			m_nNumericPrecision;
	short			m_nNumericScale;
	ULONG			m_ulDateTimePrecision;
	WCHAR			m_szCharSetCatalog[129];
	WCHAR			m_szCharSetSchema[129];
	WCHAR			m_szCharSetName[129];
	WCHAR			m_szCollationCatalog[129];
	WCHAR			m_szCollationSchema[129];
	WCHAR			m_szCollationName[129];
	WCHAR			m_szDomainCatalog[129];
	WCHAR			m_szDomainSchema[129];
	WCHAR			m_szDomainName[129];
	WCHAR			m_szDescription[129];

	CCOLUMNSRow()
	{
		ClearMembers();
	}

	void ClearMembers()
	{
		m_szTableCatalog[0] = NULL;
		m_szTableSchema[0] = NULL;
		m_szTableName[0] = NULL;
		m_szColumnName[0] = NULL;
		m_guidColumn = GUID_NULL;
		m_ulColumnPropID = 0;
		m_ulOrdinalPosition = 0;
		m_bColumnHasDefault = ATL_VARIANT_FALSE;
		m_szColumnDefault[0] = NULL;
		m_ulColumnFlags = 0;
		m_bIsNullable = ATL_VARIANT_FALSE;
		m_nDataType = 0;
		m_guidType = GUID_NULL;
		m_ulCharMaxLength = 0;
		m_ulCharOctetLength = 0;
		m_nNumericPrecision = 0;
		m_nNumericScale = 0;
		m_ulDateTimePrecision = 0;
		m_szCharSetCatalog[0] = NULL;
		m_szCharSetSchema[0] = NULL;
		m_szCharSetName[0] = NULL;
		m_szCollationCatalog[0] = NULL;
		m_szCollationSchema[0] = NULL;
		m_szCollationName[0] = NULL;
		m_szDomainCatalog[0] = NULL;
		m_szDomainSchema[0] = NULL;
		m_szDomainName[0] = NULL;
		m_szDescription[0] = NULL;
	}


BEGIN_PROVIDER_COLUMN_MAP(CCOLUMNSRow)
	PROVIDER_COLUMN_ENTRY_WSTR("TABLE_CATALOG", 1, m_szTableCatalog)
	PROVIDER_COLUMN_ENTRY_WSTR("TABLE_SCHEMA", 2, m_szTableSchema)
	PROVIDER_COLUMN_ENTRY_WSTR("TABLE_NAME", 3, m_szTableName)
	PROVIDER_COLUMN_ENTRY_WSTR("COLUMN_NAME", 4, m_szColumnName)
	PROVIDER_COLUMN_ENTRY_PS("COLUMN_GUID", 5, 0xFF, 0xFF, m_guidColumn)
	PROVIDER_COLUMN_ENTRY_PS("COLUMN_PROPID",6, 10, 0xFF, m_ulColumnPropID)
	PROVIDER_COLUMN_ENTRY_PS("ORDINAL_POSITION",7, 10, 0xFF, m_ulOrdinalPosition)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("COLUMN_HASDEFAULT",8, DBTYPE_BOOL, 0xFF, 0xFF, m_bColumnHasDefault)
	PROVIDER_COLUMN_ENTRY_PS("COLUMN_DEFAULT",9, 0xFF, 0xFF, m_szColumnDefault)
	PROVIDER_COLUMN_ENTRY_PS("COLUMN_FLAGS",10, 10, 0xFF, m_ulColumnFlags)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("IS_NULLABLE",11, DBTYPE_BOOL, 0xFF, 0xFF, m_bIsNullable)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("DATA_TYPE",12, DBTYPE_UI2, 5, 0xFF, m_nDataType)
	PROVIDER_COLUMN_ENTRY_PS("TYPE_GUID",13, 0xFF, 0xFF, m_guidType)
	PROVIDER_COLUMN_ENTRY_PS("CHARACTER_MAXIMUM_LENGTH",14, 10, 0xFF, m_ulCharMaxLength)
	PROVIDER_COLUMN_ENTRY_PS("CHARACTER_OCTET_LENGTH",15, 10, 0xFF, m_ulCharOctetLength)
	PROVIDER_COLUMN_ENTRY_PS("NUMERIC_PRECISION",16, 5, 0xFF, m_nNumericPrecision)
	PROVIDER_COLUMN_ENTRY_PS("NUMERIC_SCALE",17, 5, 0xFF, m_nNumericScale)
	PROVIDER_COLUMN_ENTRY_PS("DATETIME_PRECISION",18, 10, 0xFF, m_ulDateTimePrecision)
	PROVIDER_COLUMN_ENTRY_WSTR("CHARACTER_SET_CATALOG", 19, m_szCharSetCatalog)
	PROVIDER_COLUMN_ENTRY_WSTR("CHARACTER_SET_SCHEMA", 20, m_szCharSetSchema)
	PROVIDER_COLUMN_ENTRY_WSTR("CHARACTER_SET_NAME", 21, m_szCharSetName)
	PROVIDER_COLUMN_ENTRY_WSTR("COLLATION_CATALOG", 22, m_szCollationCatalog)
	PROVIDER_COLUMN_ENTRY_WSTR("COLLATION_SCHEMA", 23, m_szCollationSchema)
	PROVIDER_COLUMN_ENTRY_WSTR("COLLATION_NAME", 24, m_szCollationName)
	PROVIDER_COLUMN_ENTRY_WSTR("DOMAIN_CATALOG", 25, m_szDomainCatalog)
	PROVIDER_COLUMN_ENTRY_WSTR("DOMAIN_SCHEMA", 26, m_szDomainSchema)
	PROVIDER_COLUMN_ENTRY_WSTR("DOMAIN_NAME", 27, m_szDomainName)
	PROVIDER_COLUMN_ENTRY_WSTR("DESCRIPTION", 28, m_szDescription)
END_PROVIDER_COLUMN_MAP()
};

template <class ArrayClass>
HRESULT InitFromRowset(
	_Inout_ ArrayClass& rgData,
	_In_ DBID* pTableID,
	_In_ DBID* pIndexID,
	_Inout_ IUnknown* pSession,
	_Out_ LONG* pcRowsAffected)
{
	CComQIPtr<IOpenRowset> spOpenRowset = pSession;
	if (spOpenRowset == NULL)
		return E_FAIL;
	CComPtr<IColumnsInfo> spColInfo;
	HRESULT hr = spOpenRowset->OpenRowset(NULL, pTableID, pIndexID, __uuidof(IColumnsInfo), 0, NULL, (IUnknown**)&spColInfo);
	if (FAILED(hr))
		return hr;
	LPOLESTR szColumns = NULL;
	DBORDINAL cColumns = 0;
	DBCOLUMNINFO* pColInfo = NULL;
	hr = spColInfo->GetColumnInfo(&cColumns, &pColInfo, &szColumns);
	if (FAILED(hr))
		return hr;
	*pcRowsAffected = 0;
	for (ULONG iCol = 0; iCol < cColumns;  iCol++)
	{
		CCOLUMNSRow crData;
		DBCOLUMNINFO& rColCur = pColInfo[iCol];
		Checked::wcsncpy_s(crData.m_szTableName, _countof(crData.m_szTableName), pTableID->uName.pwszName, _TRUNCATE);
		Checked::wcsncpy_s(crData.m_szColumnName, _countof(crData.m_szColumnName), rColCur.pwszName, _TRUNCATE);
		Checked::wcsncpy_s(crData.m_szDescription, _countof(crData.m_szDescription), rColCur.pwszName, _TRUNCATE);
		GUID* pGuidCol = CDBIDOps::GetDBIDpGuid(rColCur.columnid);
		if (pGuidCol)
			crData.m_guidColumn = *pGuidCol;
		else
			crData.m_guidColumn = GUID_NULL;
		crData.m_ulColumnPropID = CDBIDOps::GetPropIDFromDBID(rColCur.columnid);
		crData.m_ulOrdinalPosition = rColCur.iOrdinal;
		crData.m_ulColumnFlags = rColCur.dwFlags;
		crData.m_bIsNullable = (rColCur.dwFlags & DBCOLUMNFLAGS_ISNULLABLE) ? ATL_VARIANT_TRUE : ATL_VARIANT_FALSE;
		crData.m_nDataType = rColCur.wType;
		crData.m_ulCharMaxLength = rColCur.ulColumnSize;
		crData.m_nNumericPrecision = rColCur.bPrecision;
		crData.m_nNumericScale = rColCur.bScale;
		//if (!rgData.Add(crData))
		//{
		//	CoTaskMemFree(pColInfo);
		//	CoTaskMemFree(szColumns);
		//	return E_OUTOFMEMORY;
		//}
		_ATLTRY
		{
			rgData.Add(crData);
		}
		_ATLCATCH( e )
		{
			_ATLDELETEEXCEPTION( e );
			CoTaskMemFree(pColInfo);
			CoTaskMemFree(szColumns);
			return E_OUTOFMEMORY;
		}

		(*pcRowsAffected)++;
	}

	CoTaskMemFree(pColInfo);
	CoTaskMemFree(szColumns);
	return S_OK;
}

class CPROVIDER_TYPERow
{
public:
// Attributes
	WCHAR			m_szName[129];
	USHORT			m_nType;
	ULONG			m_ulSize;
	WCHAR			m_szPrefix[129];
	WCHAR			m_szSuffix[129];
	WCHAR			m_szCreateParams[129];
	VARIANT_BOOL	m_bIsNullable;
	VARIANT_BOOL	m_bCaseSensitive;
	ULONG			m_ulSearchable;
	VARIANT_BOOL	m_bUnsignedAttribute;
	VARIANT_BOOL	m_bFixedPrecScale;
	VARIANT_BOOL	m_bAutoUniqueValue;
	WCHAR			m_szLocalTypeName[129];
	short			m_nMinScale;
	short			m_nMaxScale;
	GUID			m_guidType;
	WCHAR			m_szTypeLib[129];
	WCHAR			m_szVersion[129];
	VARIANT_BOOL	m_bIsLong;
	VARIANT_BOOL	m_bBestMatch;
	VARIANT_BOOL	m_bIsFixedLength;


	CPROVIDER_TYPERow()
	{
		m_szName[0] = NULL;
		m_nType = 0;
		m_ulSize = 0;
		m_szPrefix[0] = NULL;
		m_szSuffix[0] = NULL;
		m_szCreateParams[0] = NULL;
		m_bIsNullable = ATL_VARIANT_FALSE;
		m_bCaseSensitive = ATL_VARIANT_FALSE;
		m_ulSearchable = DB_UNSEARCHABLE;
		m_bUnsignedAttribute = ATL_VARIANT_FALSE;
		m_bFixedPrecScale = ATL_VARIANT_FALSE;
		m_bAutoUniqueValue = ATL_VARIANT_FALSE;
		m_szLocalTypeName[0] = NULL;
		m_nMinScale = 0;
		m_nMaxScale = 0;
		m_guidType = GUID_NULL;
		m_szTypeLib[0] = NULL;
		m_szVersion[0] = NULL;
		m_bIsLong = ATL_VARIANT_FALSE;
		m_bBestMatch = ATL_VARIANT_FALSE;
		m_bIsFixedLength = ATL_VARIANT_FALSE;
	}
// Binding Maps
BEGIN_PROVIDER_COLUMN_MAP(CPROVIDER_TYPERow)
	PROVIDER_COLUMN_ENTRY_WSTR("TYPE_NAME", 1, m_szName)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("DATA_TYPE", 2, DBTYPE_UI2, 5, 0xFF, m_nType)
	PROVIDER_COLUMN_ENTRY_PS("COLUMN_SIZE", 3, 10, 0xFF, m_ulSize)
	PROVIDER_COLUMN_ENTRY_WSTR("LITERAL_PREFIX", 4, m_szPrefix)
	PROVIDER_COLUMN_ENTRY_WSTR("LITERAL_SUFFIX", 5, m_szSuffix)
	PROVIDER_COLUMN_ENTRY_WSTR("CREATE_PARAMS", 6, m_szCreateParams)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("IS_NULLABLE", 7, DBTYPE_BOOL, 0xFF, 0xFF, m_bIsNullable)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("CASE_SENSITIVE", 8, DBTYPE_BOOL, 0xFF, 0xFF, m_bCaseSensitive)
	PROVIDER_COLUMN_ENTRY_PS("SEARCHABLE", 9, 10, 0xFF, m_ulSearchable)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("UNSIGNED_ATTRIBUTE", 10, DBTYPE_BOOL, 0xFF, 0xFF, m_bUnsignedAttribute)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("FIXED_PREC_SCALE", 11, DBTYPE_BOOL, 0xFF, 0xFF, m_bFixedPrecScale)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("AUTO_UNIQUE_VALUE", 12, DBTYPE_BOOL, 0xFF, 0xFF, m_bAutoUniqueValue)
	PROVIDER_COLUMN_ENTRY_WSTR("LOCAL_TYPE_NAME", 13, m_szLocalTypeName)
	PROVIDER_COLUMN_ENTRY_PS("MINIMUM_SCALE", 14, 5, 0xFF, m_nMinScale)
	PROVIDER_COLUMN_ENTRY_PS("MAXIMUM_SCALE", 15, 5, 0xFF, m_nMaxScale)
	PROVIDER_COLUMN_ENTRY_PS("GUID", 16, 0xFF, 0xFF, m_guidType)
	PROVIDER_COLUMN_ENTRY_WSTR("TYPELIB", 17, m_szTypeLib)
	PROVIDER_COLUMN_ENTRY_WSTR("VERSION", 18, m_szVersion)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("IS_LONG", 19, DBTYPE_BOOL, 0xFF, 0xFF, m_bIsLong)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("BEST_MATCH", 20, DBTYPE_BOOL, 0xFF, 0xFF, m_bBestMatch)
	PROVIDER_COLUMN_ENTRY_TYPE_PS("IS_FIXEDLENGTH", 21, DBTYPE_BOOL, 0xFF, 0xFF, m_bIsFixedLength)
END_PROVIDER_COLUMN_MAP()
};


class CEnumRowsetImpl
{
public:

	WCHAR m_szSourcesName[256];
	WCHAR m_szSourcesParseName[256];
	WCHAR m_szSourcesDescription[256];
	unsigned short m_iType;
	VARIANT_BOOL m_bIsParent;

BEGIN_PROVIDER_COLUMN_MAP(CEnumRowsetImpl)
	PROVIDER_COLUMN_ENTRY("SOURCES_NAME", 1, m_szSourcesName)
	PROVIDER_COLUMN_ENTRY("SOURCES_PARSENAME", 2, m_szSourcesParseName)
	PROVIDER_COLUMN_ENTRY("SOURCES_DESCRIPTION", 3, m_szSourcesDescription)
	PROVIDER_COLUMN_ENTRY("SOURCES_TYPE", 4, m_iType)
	PROVIDER_COLUMN_ENTRY("SOURCES_ISPARENT", 5, m_bIsParent)
END_PROVIDER_COLUMN_MAP()

};


///////////////////////////////////////////////////////////////////////////
// class IRowsetUpdateImpl

template <class T, class Storage,
			class UpdateArray = CAtlArray<Storage>,
			class RowClass = CSimpleRow,
			class MapClass = CAtlMap < RowClass::KeyType, RowClass* > >
class IRowsetUpdateImpl :
	public IRowsetChangeImpl<T, Storage, IRowsetUpdate, RowClass, MapClass>
{
public:
	// Destructor
	virtual ~IRowsetUpdateImpl()
	{
		m_mapCachedData.RemoveAll();
	}

	// IRowsetChange Methods
	STDMETHOD (SetData)(
		_In_ HROW hRow,
		_In_ HACCESSOR hAccessor,
		_In_opt_ void* pSrcData)
	{
		T* pT = (T*)this;

		_ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();
		}

		// We provide this implementation for use as it adds more
		// complexity to the SetData function to handle both
		// immediate and deferred processing.  It also allows us to
		// cache the original data w/o increasing the size of
		// IRowsetChangeImpl
		RowClass* pRow;
		HRESULT hr;

		// Determine if we are in immediate or deferred mode, if we are in
		// immediate mode call FlushData.  Otherwise, add the orginal data
		// to the cache
		CComVariant varDeferred;
		bool bDeferred;
		hr = pT->GetPropValue(&DBPROPSET_ROWSET, DBPROP_IRowsetUpdate,
							&varDeferred);
		(FAILED(hr) || varDeferred.boolVal == ATL_VARIANT_FALSE) ? bDeferred = false : bDeferred = true;

		if( ! pT->m_rgRowHandles.Lookup(hRow, pRow) )
			return DB_E_BADROWHANDLE;

		if (hRow == NULL || pRow == NULL)
			return DB_E_BADROWHANDLE;

		// If we've deleted the row (or are pending) then SetData should fail
		if (pRow->m_status == DBPENDINGSTATUS_DELETED)
			return DB_E_DELETEDROW;

		if (bDeferred)
		{
			_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
			{
				if (pRow->m_status != DBPENDINGSTATUS_CHANGED &&
					pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
				{
					HRESULT hrNotify = pT->Fire_OnRowChange(pT, 1, &hRow,
						DBREASON_ROW_FIRSTCHANGE, DBEVENTPHASE_OKTODO, FALSE);
					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
						return DB_E_CANCELED;

					hrNotify = pT->Fire_OnRowChange(pT, 1, &hRow,
						DBREASON_ROW_FIRSTCHANGE, DBEVENTPHASE_ABOUTTODO, FALSE);
					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
						return DB_E_CANCELED;
				}
			}

			// If a row is soft inserted (i.e. haven't called update yet)
			// then ignore this section.  An undo will cause the row to
			// delete.
			if (pRow->m_status != DBPENDINGSTATUS_NEW)
			{
				// See if row is already cached, don't make any changes
				//void* pData = (void*)m_mapCachedData.Lookup(hRow, pTemp);
				Storage* pData;
				if (! m_mapCachedData.Lookup(hRow, pData) )
				{
					Storage* pam = NULL;
					ATLTRY(pam = _ATL_NEW Storage)
					if (pam == NULL)
						return E_OUTOFMEMORY;
					Checked::memcpy_s(pam, sizeof(Storage), &pT->m_rgRowData[pRow->m_iRowset], sizeof(Storage));

					_ATLTRY
					{
						m_mapCachedData.SetAt(hRow, pam);
					}
					_ATLCATCH( e )
					{
						_ATLDELETEEXCEPTION( e );
						return E_FAIL;
					}
				}
			}
		}

		// NOTE: TransferData will send the SYNCHAFTER phase of the FIRSTCHANGE
		// notification message.  It will handle a veto internally.
		hr = TransferData<T, RowClass, MapClass>
						(pT, false, pSrcData, pRow, &(pT->m_rgRowHandles), hAccessor);
		if (FAILED(hr))
			return hr;

		// Flush Users Data, if in immediate mode
		if (!bDeferred)
		{
			HRESULT hrFlush = pT->FlushData(hRow, hAccessor);
			if (hrFlush == S_OK)
				return hr;  // TransferData may have returned DB_S_ERRORSOCCURRED
			else
				return hrFlush;
		}
		else
		{
			_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
			{
				if (pRow->m_status != DBPENDINGSTATUS_CHANGED &&
					pRow->m_status != (DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_UNCHANGED))
				{
					pT->Fire_OnRowChange(pT, 1, &hRow,
						DBREASON_ROW_FIRSTCHANGE, DBEVENTPHASE_DIDEVENT, TRUE);
				}
			}

			if (pRow->m_status != DBPENDINGSTATUS_NEW)
				pRow->m_status = DBPENDINGSTATUS_CHANGED;
			return hr;	// TransferData may have returned DB_S_ERRORSOCCURRED
		}
	}

	// IRowsetUpdate Methods
	STDMETHOD (GetOriginalData)(
		_In_ HROW hRow,
		_In_ HACCESSOR hAccessor,
		_Inout_opt_ void* pData)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetUpdateImpl::GetOriginalData\n"));

		T* pT = (T*)this;

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
				return DB_E_NOTREENTRANT;
			else
				pT->DecrementMutex();
		}

		// Validate input parameters
		typename T::_BindType* pBinding;
		bool bFound = pT->m_rgBindings.Lookup((INT_PTR)hAccessor, pBinding);
		if (!bFound || pBinding == NULL)
			return DB_E_BADACCESSORHANDLE;

		if (pData == NULL && pBinding->cBindings != 0)
			return E_INVALIDARG;

		RowClass* pRow;
		if (! pT->m_rgRowHandles.Lookup(hRow, pRow))
			return DB_E_BADROWHANDLE;
		if (hRow == NULL || pRow == NULL)
			return DB_E_BADROWHANDLE;

		// If the status is DBPENDINGSTATUS_INVALIDROW, the row has been
		// deleted and the change transmitted to the data source.  In
		// this case, we can't get the original data so return
		// DB_E_DELETEDROW.
		if (pRow->m_status == DBPENDINGSTATUS_INVALIDROW)
			return DB_E_DELETEDROW;

		// Note, need to determine if accessor is not a row accessor


		// Determine if we have a pending insert. In this case, the
		// spec says revert to default values, and if defaults,
		// are not available, then NULLs.

		if (pRow->m_status == DBPENDINGSTATUS_NEW)
		{
			ATLCOLUMNINFO* pInfo;
			bool bSucceeded = true;
			bool bFailed = false;
			DBORDINAL ulColumns;
			Storage temp;
			Checked::memcpy_s(&temp, sizeof(Storage), &pT->m_rgRowData[pRow->m_iRowset], sizeof(Storage));

			pInfo = T::GetColumnInfo(pT, &ulColumns);

			for (ULONG lBind=0; lBind<pBinding->cBindings; lBind++)
			{
				DBBINDING* pBindCur = &(pBinding->pBindings[lBind]);
				ULONG lColInfo;
				for (lColInfo=0; lColInfo<ulColumns &&
					pBindCur->iOrdinal != pInfo[lColInfo].iOrdinal; lColInfo++);
				if (lColInfo == ulColumns)
					return DB_E_BADORDINAL;

				ATLCOLUMNINFO* pColCur = &(pInfo[lColInfo]);
				DBSTATUS dbStat = DBSTATUS_S_DEFAULT;

				// Try to get the default value.  if that doesn't work, then
				// attempt to do a NULL value
				if (FAILED(pT->SetDBStatus(&dbStat, pRow, pColCur)))
				{
					if ((pColCur->dwFlags & DBCOLUMNFLAGS_ISNULLABLE) ||
						(pColCur->dwFlags & DBCOLUMNFLAGS_MAYBENULL))
					{
						BYTE* pDstTemp = (BYTE*)(&(pT->m_rgRowData[pRow->m_iRowset]) + pColCur->cbOffset);
						*pDstTemp = NULL;
						if (pBindCur->dwPart & DBPART_STATUS)
							*((DBSTATUS*)((BYTE*)(pData) + pBindCur->obStatus)) = DBSTATUS_S_ISNULL;
						bSucceeded |= true;
						continue;
					}
					else
					{
						if (pBindCur->dwPart & DBPART_STATUS)
							*((DBSTATUS*)((BYTE*)(pData) + pBindCur->obStatus)) = DBSTATUS_E_UNAVAILABLE;
						bFailed = true;
						continue;
					}
				}
				else
				{
					if (pBindCur->dwPart & DBPART_STATUS)
						*((DBSTATUS*)((BYTE*)(pData) + pBindCur->obStatus)) = DBSTATUS_S_OK;
					bSucceeded |= true;
					continue;
				}
			}

			TransferData<T, RowClass, MapClass>
					(pT, true, pData, pRow, &(pT->m_rgRowHandles), hAccessor);

			Checked::memcpy_s(&pT->m_rgRowData[pRow->m_iRowset], sizeof(Storage), &temp, sizeof(Storage));
			if (!bFailed)
				return S_OK;
			else
				return (bSucceeded ? DB_S_ERRORSOCCURRED : DB_E_ERRORSOCCURRED);
		}

		// Determine if the row is cached, if so, we'll temporarily replace
		// it in m_rgRowData and call TransferData to handle the accessor.
		// It is kind of strange but it saves us from having to reimplement
		// TransferData

		Storage* pam;
		bFound = m_mapCachedData.Lookup(hRow, pam);
		Storage temp;

		if (bFound && pam != NULL)
		{
			Checked::memcpy_s(&temp, sizeof(Storage), &pT->m_rgRowData[pRow->m_iRowset], sizeof(Storage));
			Checked::memcpy_s(&pT->m_rgRowData[pRow->m_iRowset], sizeof(Storage), pam, sizeof(Storage));
		}

		// We need to trick TransferData to thinking a non-transmitted
		// deleted row is still alive.  This will be its final state after
		// we return anyway.
		if (pRow->m_status == DBPENDINGSTATUS_DELETED)
			pRow->m_status = DBPENDINGSTATUS_UNCHANGED;

		HRESULT hr = TransferData<T, RowClass, MapClass>
						(pT, true, pData, pRow, &(pT->m_rgRowHandles), hAccessor);

		if (bFound && pam != NULL)
		{
			Checked::memcpy_s(&pT->m_rgRowData[pRow->m_iRowset], sizeof(Storage), &temp, sizeof(Storage));
		}

		pRow->m_status = DBPENDINGSTATUS_UNCHANGED;
		return hr;
	}

	STDMETHOD (GetPendingRows)(
		_In_ HCHAPTER /*hReserved*/,
		_In_ DBPENDINGSTATUS dwRowStatus,
		_Out_opt_ DBCOUNTITEM* pcPendingRows,
		_Outptr_opt_result_maybenull_ HROW** prgPendingRows,
		_Outptr_opt_result_maybenull_ DBPENDINGSTATUS** prgPendingStatus)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetUpdateImpl::GetPendingRows\n"));
		T* pT = (T*)this;
		bool bPending = false;
		RowClass* pRow = NULL;
		//DBROWCOUNT ulRowHandles = (DBROWCOUNT)pT->m_rgRowHandles.GetCount();

		if (pcPendingRows != NULL)
		{
			*pcPendingRows = 0;

			if (prgPendingRows != NULL)
				*prgPendingRows = NULL;

			if (prgPendingStatus != NULL)
				*prgPendingStatus = NULL;
		}

		// Validate input parameters
		if ((dwRowStatus &
			~(DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_CHANGED | DBPENDINGSTATUS_DELETED)) != 0)
			return E_INVALIDARG;

		// Validate input parameters
		if ((dwRowStatus &
			~(DBPENDINGSTATUS_NEW | DBPENDINGSTATUS_CHANGED | DBPENDINGSTATUS_DELETED)) != 0)
			return E_INVALIDARG;

		// Determine how many rows we'll need to return

		POSITION pos = pT->m_rgRowHandles.GetStartPosition();
		while( pos != NULL )
		{
			typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext( pos );
			ATLASSERT( pPair != NULL );
			if(!pPair)
			{
				return E_FAIL;
			}

			// Check to see if a row has a pending status
			pRow = pPair->m_value;

			if (pRow->m_status & dwRowStatus)
			{
				if (pcPendingRows != NULL)
					(*pcPendingRows)++;
				bPending = true;
			}
		}

		// In this case, there are no pending rows that match, just exit out
		if (!bPending)
		{
			// There are no pending rows so exit immediately
			return S_FALSE;
		}
		else
		{
			// Here' the consumer just wants to see if there are pending rows
			// we know that so we can exit
			if (pcPendingRows == NULL)
				return S_OK;
		}

		// Allocate arrays for pending rows
		if (prgPendingRows != NULL)
		{
			*prgPendingRows = (HROW*)::ATL::AtlCoTaskMemCAlloc(*pcPendingRows, static_cast<ULONG>(sizeof(HROW)));
			if (*prgPendingRows == NULL)
			{
				*pcPendingRows = 0;
				return E_OUTOFMEMORY;
			}
		}

		if (prgPendingStatus != NULL)
		{
			*prgPendingStatus = (DBPENDINGSTATUS*)::ATL::AtlCoTaskMemCAlloc(*pcPendingRows, static_cast<ULONG>(sizeof(DBPENDINGSTATUS)));
			if (*prgPendingStatus == NULL)
			{
				*pcPendingRows = 0;
				CoTaskMemFree(*prgPendingRows);
				*prgPendingRows = NULL;
				return E_OUTOFMEMORY;
			}
			memset(*prgPendingStatus, 0, *pcPendingRows * sizeof(DBPENDINGSTATUS));
		}

		if (prgPendingRows || prgPendingStatus)
		{
			ULONG ulRows = 0;
			pos = pT->m_rgRowHandles.GetStartPosition();
			while( pos != NULL )
			{
				typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext( pos );
				ATLASSERT( pPair != NULL );

				pRow = pPair->m_value;
				if (pRow->m_status & dwRowStatus)
				{
					// Add the output row
					pRow->AddRefRow();
					if (prgPendingRows)
						((*prgPendingRows)[ulRows]) = /*(HROW)*/pPair->m_key;
					if (prgPendingStatus)
						((*prgPendingStatus)[ulRows]) = (DBPENDINGSTATUS)pRow->m_status;
					ulRows++;
				}
			}
			/*
			for (iRowset = 0; iRowset < ulRowHandles; iRowset++)
			{
				pRow = pT->m_rgRowHandles.GetValueAt(iRowset);
				if (pRow->m_status & dwRowStatus)
				{
					// Add the output row
					pRow->AddRefRow();
					if (prgPendingRows)
						((*prgPendingRows)[ulRows]) = (HROW)pT->m_rgRowHandles.GetKeyAt(iRowset);
					if (prgPendingStatus)
						((*prgPendingStatus)[ulRows]) = (DBPENDINGSTATUS)pRow->m_status;
					ulRows++;
				}
			}
			*/
			if (pcPendingRows != NULL)
				*pcPendingRows = ulRows;
		}

		// Return code depending on
		return S_OK;
	}

	STDMETHOD (GetRowStatus)(
		_In_ HCHAPTER /*hReserved*/,
		_In_ DBCOUNTITEM cRows,
		_In_reads_(cRows) const HROW rghRows[],
		_Out_writes_(cRows) DBPENDINGSTATUS rgPendingStatus[])
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetUpdateImpl::GetRowStatus\n"));
		T* pT = (T*)this;

		bool bSucceeded = true;
		ULONG ulFetched = 0;

		if (cRows != 0)
		{
			// check for correct pointers
			if (rghRows == NULL || rgPendingStatus == NULL)
				return E_INVALIDARG;

			for (ULONG ulRows=0; ulRows < cRows; ulRows++)
			{
				RowClass* pRow;
				bool bFound = pT->m_rgRowHandles.Lookup(rghRows[ulRows], pRow);
				if ((! bFound || pRow == NULL) || (pRow->m_status == DBPENDINGSTATUS_INVALIDROW))
				{
					rgPendingStatus[ulRows] = DBPENDINGSTATUS_INVALIDROW;
					bSucceeded = false;
					continue;
				}
				if (pRow->m_status != 0)
					rgPendingStatus[ulRows] = pRow->m_status;
				else
					rgPendingStatus[ulRows] = DBPENDINGSTATUS_UNCHANGED;

				ulFetched++;
			}
		}

		if (bSucceeded)
		{
			return S_OK;
		}
		else
		{
			if (ulFetched > 0)
				return DB_S_ERRORSOCCURRED;
			else
				return DB_E_ERRORSOCCURRED;
		}
	}

	STDMETHOD (Undo)(
		_In_ HCHAPTER /*hReserved*/,
		_In_ DBCOUNTITEM cRows,
		_In_reads_(cRows) const HROW rghRows[],
		_Out_ DBCOUNTITEM *pcRowsUndone,
		_Outptr_opt_result_buffer_maybenull_(*pcRowsUndone) HROW **prgRowsUndone,
		_Outptr_opt_result_buffer_maybenull_(*pcRowsUndone) DBROWSTATUS **prgRowStatus)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetUpdateImpl::Undo\n"));

		T* pT = (T*)this;
		typename T::ObjectLock lock(pT);

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
			{
				// Note, we can't set this up above as we may inadvertently
				// step on the pcRowsUndone variable.
				if (pcRowsUndone != NULL)
					*pcRowsUndone = NULL;
				return DB_E_NOTREENTRANT;
			}
			else
				pT->DecrementMutex();
		}

		DBCOUNTITEM ulRows = 0;
		bool bSucceeded = false;
		bool bFailed = false;
		bool bIgnore = false;
		ULONG ulUndone = 0;

		// the following lines are used to fix the two _alloca calls below.  Those calls are risky
		// because we may be allocating huge amounts of data.  So instead I'll allocate that data on heap.
		// But if you use _alloca you don't have to worry about cleaning this memory.  So we will use these
		// temporary variables to allocate memory on heap.  As soon as we exit the function, the memory will
		// be cleaned up, just as if we were using alloca. So now, instead of calling alloca, I'll alloc
		// memory on heap using the two smart pointers below, and then assigning it to the actual pointers.
		CHeapPtr<HROW> spTempRowsUndone;
		CHeapPtr<DBROWSTATUS> spTempRowStatus;

		HRESULT hrNotify = S_OK;
		int nReason = 0;

		// Ignore prgRowsUndone/prgRowStatus if pcRowsUndone is NULL and
		// cRows == 0.
		if (pcRowsUndone != NULL || cRows != 0)
		{
			if (prgRowsUndone != NULL)
				*prgRowsUndone = NULL;

			if (prgRowStatus != NULL)
				*prgRowStatus = NULL;
		}
		else
		{
			bIgnore = true;		// Don't do status or row arrays
		}

		// Check to see how many changes we'll undo
		if (pcRowsUndone != NULL)
		{
			*pcRowsUndone = NULL;
			 if (prgRowsUndone == NULL)
				 return E_INVALIDARG;
		}


		if (cRows != 0)
		{
			if (rghRows == NULL)
				return E_INVALIDARG;

			ulRows = cRows;
		}
		else
			ulRows = (DBCOUNTITEM)pT->m_rgRowHandles.GetCount();

		// NULL out pointers
		if (prgRowsUndone != NULL && ulRows != 0 && bIgnore == false)
		{
			// Make a temporary buffer as we may not fill up everything
			// in the case where cRows == 0
			if (cRows != 0)
				*prgRowsUndone = (HROW*)::ATL::AtlCoTaskMemCAlloc(ulRows, static_cast<ULONG>(sizeof(HROW)));
			else
			{
				spTempRowsUndone.Allocate(ulRows);
				*prgRowsUndone = spTempRowsUndone;
			}

			if (*prgRowsUndone == NULL)
				return E_OUTOFMEMORY;
		}
		else
		{
			if (prgRowsUndone != NULL && bIgnore == false)
				*prgRowsUndone = NULL;
		}

		if (prgRowStatus != NULL && ulRows != 0 && bIgnore == false)
		{
			if (cRows != 0)
				*prgRowStatus = (DBROWSTATUS*)::ATL::AtlCoTaskMemCAlloc(ulRows, static_cast<ULONG>(sizeof(DBROWSTATUS)));
			else
			{
				spTempRowStatus.Allocate(ulRows);
				*prgRowStatus = spTempRowStatus;
			}

			if (*prgRowStatus == NULL)
			{
				if (cRows != 0)
					CoTaskMemFree(*prgRowsUndone);
				*prgRowsUndone = NULL;
				return E_OUTOFMEMORY;
			}
		}
		else
		{
			if (prgRowStatus != NULL && bIgnore == false)
				*prgRowStatus = NULL;
		}

		POSITION pos = pT->m_rgRowHandles.GetStartPosition();
		RowClass* pRow = NULL;
		for (ULONG ulUndoRow = 0; ulUndoRow < ulRows; ulUndoRow++)
		{
			HROW hRowUndo = NULL;
			ULONG ulCurrentRow = ulUndone;

			if (rghRows != NULL && cRows != 0)
			{
				hRowUndo = rghRows[ulUndoRow];
			}
			else
			{
				ATLASSERT(ulUndoRow < (ULONG)pT->m_rgRowHandles.GetCount());
				ATLASSERT( pos != NULL );
				typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext(pos);
				ATLASSERT( pPair != NULL );
				if(!pPair)
				{
					return E_FAIL;
				}
				hRowUndo = pPair->m_key;
			}

			if (prgRowsUndone != NULL && bIgnore == false)
				(*prgRowsUndone)[ulCurrentRow] = hRowUndo;

			// Fetch the RowClass and determine if it is valid
			bool bFound = pT->m_rgRowHandles.Lookup(hRowUndo, pRow);
			if (!bFound || pRow == NULL)
			{
				if (prgRowStatus != NULL && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_INVALID;
				bFailed = true;
				ulUndone++;
				continue;
			}

			// If cRows is zero we'll go through all rows fetched.  We shouldn't
			// increment the count for rows that haven't been modified.

			if (cRows != 0 || (pRow != NULL &&
				pRow->m_status != 0 && pRow->m_status != DBPENDINGSTATUS_UNCHANGED
				&& pRow->m_status != DBPENDINGSTATUS_INVALIDROW))
				ulUndone++;
			else
				continue;


			// AddRef the row if cRows is zero (we will be returning it to the
			// consumer)
			if (cRows == 0)
				pRow->AddRefRow();

			// Fetch the data, note, this may be NULL in several cases
			Storage* pData;
			if(! m_mapCachedData.Lookup(hRowUndo, pData) )
				pData = NULL;

			switch (pRow->m_status)
			{
			case DBPENDINGSTATUS_INVALIDROW:	// Row has already been hard deleted
				if (prgRowStatus != NULL && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_DELETED;
				bFailed = true;
				continue;
				break;

			case DBPENDINGSTATUS_NEW:			// Row has been soft inserted
				// If the row is newly inserted, go ahead and mark its
				// row as INVALID (according to the specification).

				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUndo,
						DBREASON_ROW_UNDOINSERT, DBEVENTPHASE_OKTODO, FALSE);

					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
					{
						if (prgRowStatus != NULL && bIgnore == false)
							(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
						bFailed = true;
						continue;
					}

					hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUndo,
						DBREASON_ROW_UNDOINSERT, DBEVENTPHASE_ABOUTTODO, FALSE);

					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
					{
						if (prgRowStatus != NULL && bIgnore == false)
							(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
						bFailed = true;
						continue;
					}

					hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUndo,
						DBREASON_ROW_UNDOINSERT, DBEVENTPHASE_SYNCHAFTER, FALSE);

					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
					{
						if (prgRowStatus != NULL && bIgnore == false)
							(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
						bFailed = true;
						continue;
					}
				}

				if (prgRowStatus != NULL && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_S_OK;
				pT->m_rgRowData.RemoveAt(pRow->m_iRowset);

				// Move any other subsequent rows up in the chain
				{
					POSITION posRow = pT->m_rgRowHandles.GetStartPosition();
					while(posRow != NULL)
					{
						typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext(posRow);
						ATLASSERT( pPair != NULL );
						if(!pPair)
						{
							return E_FAIL;
						}
						RowClass* pCheckRow = pPair->m_value;
						if (pCheckRow != NULL &&
							pCheckRow->m_iRowset > pRow->m_iRowset)
							pCheckRow->m_iRowset--;
					}
				}

				pRow->m_status = DBPENDINGSTATUS_INVALIDROW;
				bSucceeded = true;

				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					pT->Fire_OnRowChange(pT, 1, &hRowUndo, DBREASON_ROW_UNDOINSERT,
						DBEVENTPHASE_DIDEVENT, TRUE);
				}

				continue;
				break;

			case 0:								// Row has just been fetched
			case DBPENDINGSTATUS_DELETED:		// Row has been soft deleted
			case DBPENDINGSTATUS_UNCHANGED:		// Row is not changed
				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					if (pRow->m_status == DBPENDINGSTATUS_DELETED)
					{
						nReason = 1;
						hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUndo,
							DBREASON_ROW_UNDODELETE, DBEVENTPHASE_OKTODO, FALSE);

						if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
						{
							if (prgRowStatus != NULL && bIgnore == false)
								(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
							bFailed = true;
							continue;
						}

						hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUndo,
							DBREASON_ROW_UNDODELETE, DBEVENTPHASE_ABOUTTODO, FALSE);

						if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
						{
							if (prgRowStatus != NULL && bIgnore == false)
								(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
							bFailed = true;
							continue;
						}

						hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUndo,
							DBREASON_ROW_UNDODELETE, DBEVENTPHASE_SYNCHAFTER, FALSE);

						if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
						{
							if (prgRowStatus != NULL && bIgnore == false)
								(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
							bFailed = true;
							continue;
						}
					}
				}

				pRow->m_status = DBPENDINGSTATUS_UNCHANGED;

				if (prgRowStatus != NULL && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_S_OK;
				bSucceeded = true;

				// Somebody set, then deleted a row most likely
				if (pData != NULL)
				{
					Checked::memcpy_s(&pT->m_rgRowData[pRow->m_iRowset], sizeof(Storage), pData, sizeof(Storage));
				}

				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					if (nReason == 1)
					{
						pT->Fire_OnRowChange(pT, 1, &hRowUndo, DBREASON_ROW_UNDODELETE,
							DBEVENTPHASE_DIDEVENT, TRUE);
						nReason = 0;
					}
				}
				continue;
				break;

			default:							// Row has been changed
				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUndo,
						DBREASON_ROW_UNDOCHANGE, DBEVENTPHASE_OKTODO, FALSE);

					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
					{
						if (prgRowStatus != NULL && bIgnore == false)
							(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
						bFailed = true;
						continue;
					}

					hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUndo,
						DBREASON_ROW_UNDOCHANGE, DBEVENTPHASE_ABOUTTODO, FALSE);

					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
					{
						if (prgRowStatus != NULL && bIgnore == false)
							(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
						bFailed = true;
						continue;
					}

					hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUndo,
						DBREASON_ROW_UNDOCHANGE, DBEVENTPHASE_SYNCHAFTER, FALSE);

					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
					{
						if (prgRowStatus != NULL && bIgnore == false)
							(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
						bFailed = true;
						continue;
					}
				}

				if (pData == NULL)
				{
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						pT->Fire_OnRowChange(pT, 1, &hRowUndo, DBREASON_ROW_UNDOCHANGE,
							DBEVENTPHASE_FAILEDTODO, FALSE);
					}

					if (prgRowStatus != NULL && bIgnore == false)
						(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_INVALID;
					bFailed = true;
					continue;
				}

				// Copy data back
				Checked::memcpy_s(&pT->m_rgRowData[pRow->m_iRowset], sizeof(Storage), pData, sizeof(Storage));
				if (prgRowStatus != NULL && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_S_OK;
				pRow->m_status = DBPENDINGSTATUS_UNCHANGED;
				bSucceeded = true;

				// Remove undo buffer
				m_mapCachedData.RemoveKey(hRowUndo);

				// Check if we need to release the row because it's ref was 0
				// See the IRowset::ReleaseRows section in the spec for more
				// information
				if (pRow->m_dwRef == 0)
				{
					pRow->AddRefRow();	// Artificially bump this to remove it
					if( FAILED( pT->RefRows(1, &hRowUndo, NULL, NULL, false) ) )
						return E_FAIL;
				}

				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					pT->Fire_OnRowChange(pT, 1, &hRowUndo, DBREASON_ROW_UNDOCHANGE,
						DBEVENTPHASE_DIDEVENT, TRUE);
				}

				break;
			}
		}

		// Set the output for rows undone.
		if (pcRowsUndone)
			*pcRowsUndone = ulUndone;

		// In the case where cRows == 0, we need to allocate the final
		// array of data.
		if (cRows == 0)
		{
			HROW* prowTemp = NULL;
			DBROWSTATUS* prowStatus = NULL;

			if (prgRowsUndone != NULL && ulUndone != 0 && bIgnore == false)
			{
				prowTemp = (HROW*)::ATL::AtlCoTaskMemCAlloc(ulUndone, static_cast<ULONG>(sizeof(HROW)));
				if (prowTemp == NULL)
				{
					// Free prgRowsUndone
					if (cRows != 0 && prgRowsUndone != NULL)
						CoTaskMemFree(*prgRowsUndone);

					if (cRows != 0 && prgRowStatus != NULL)
						CoTaskMemFree(*prgRowStatus);

					return E_OUTOFMEMORY;
				}

				Checked::memcpy_s(prowTemp, (sizeof(HROW)*ulUndone), *prgRowsUndone, (sizeof(HROW)*ulUndone));
				if (cRows != 0)
					CoTaskMemFree(*prgRowsUndone);  // we're finished w/ the temp array
				*prgRowsUndone = prowTemp;
			}
			else
			{
				if (prgRowsUndone != NULL && bIgnore == false)
					*prgRowsUndone = NULL;
			}

			if (prgRowStatus != NULL && ulUndone != 0 && bIgnore == false)
			{
				prowStatus = (DBROWSTATUS*)::ATL::AtlCoTaskMemCAlloc(ulUndone, static_cast<ULONG>(sizeof(DBROWSTATUS)));
				if (prowStatus == NULL)
				{
					if (cRows != 0 && prgRowsUndone != NULL)
						CoTaskMemFree(*prgRowsUndone);

					if (prgRowStatus != NULL && cRows != 0)
						CoTaskMemFree(*prgRowStatus);

					return E_OUTOFMEMORY;
				}

				Checked::memcpy_s(prowStatus, (sizeof(DBROWSTATUS)*ulUndone), *prgRowStatus, (sizeof(DBROWSTATUS)*ulUndone));
				if (cRows != 0)
					CoTaskMemFree(*prgRowStatus);
				*prgRowStatus = prowStatus;
			}
			else
			{
				if (prgRowStatus != NULL && bIgnore == false)
					*prgRowStatus = NULL;
			}
		}

		// Send the return value
		if (!bFailed)
			return S_OK;
		else
		{
			if (!bSucceeded)
				return DB_E_ERRORSOCCURRED;
			else
				return DB_S_ERRORSOCCURRED;
		}
	}

	STDMETHOD (Update)(
		_In_ HCHAPTER /*hReserved*/,
		_In_ DBCOUNTITEM cRows,
		_In_reads_(cRows) const HROW rghRows[],
		_Out_ DBCOUNTITEM *pcRows,
		_Outptr_opt_result_buffer_maybenull_(*pcRows) HROW **prgRows,
		_Outptr_opt_result_buffer_maybenull_(*pcRows) DBROWSTATUS **prgRowStatus)
	{
		ATLTRACE(atlTraceDBProvider, 2, _T("IRowsetUpdateImpl::Update\n"));

		T* pT = (T*)this;
		typename T::ObjectLock lock(pT);

		_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
		{
			// Check to see if someone is in an event handler.  If we do, then
			// we should return DB_E_NOTREENTRANT.
			if (!pT->IncrementMutex())
			{
				if (pcRows != NULL)
					*pcRows = NULL;
				return DB_E_NOTREENTRANT;
			}
			else
				pT->DecrementMutex();
		}

		bool bSucceeded = false;
		bool bFailed = false;
		bool bIgnore = false;
		ULONG ulCount = 0;
		DBCOUNTITEM ulRowUpdate = 0;

		// the following lines are used to fix the two _alloca calls below.  Those calls are risky
		// because we may be allocating huge amounts of data.  So instead I'll allocate that data on heap.
		// But if you use _alloca you don't have to worry about cleaning this memory.  So we will use these
		// temporary variables to allocate memory on heap.  As soon as we exit the function, the memory will
		// be cleaned up, just as if we were using alloca. So now, instead of calling alloca, I'll alloc
		// memory on heap using the two smnart pointers below, and then assing it to the actual pointers.
		CHeapPtr<HROW> spTempRows;
		CHeapPtr<DBROWSTATUS> spTempRowStatus;

		if (pcRows != NULL || cRows != 0)
		{
			if (prgRows != NULL)
				*prgRows = NULL;

			if (prgRowStatus != NULL)
				*prgRowStatus = NULL;
		}
		else
		{
			bIgnore = true;
		}

		if (pcRows != NULL)
		{
			*pcRows = NULL;
			if (prgRows == NULL)
				return E_INVALIDARG;
		}

		if (cRows != 0)
		{
			if (rghRows == NULL)
				return E_INVALIDARG;

			ulRowUpdate = cRows;
		}
		else
			ulRowUpdate = (DBCOUNTITEM)pT->m_rgRowHandles.GetCount();

		// NULL out pointers
		if (prgRows != NULL && ulRowUpdate != 0  && bIgnore == false)
		{
			if (cRows != 0)
				*prgRows = (HROW*)::ATL::AtlCoTaskMemCAlloc(ulRowUpdate, static_cast<ULONG>(sizeof(HROW)));
			else
			{
				spTempRows.Allocate(ulRowUpdate);
				*prgRows = spTempRows;
			}

			if (*prgRows == NULL)
				return E_OUTOFMEMORY;
		}
		else
		{
			if (prgRows != NULL && bIgnore == false)
				*prgRows = NULL;
		}

		if (prgRowStatus != NULL && ulRowUpdate != 0  && bIgnore == false)
		{
			if (cRows != 0)
				*prgRowStatus = (DBROWSTATUS*)::ATL::AtlCoTaskMemCAlloc(ulRowUpdate, static_cast<ULONG>(sizeof(DBROWSTATUS)));
			else
			{
				spTempRowStatus.Allocate(ulRowUpdate);
				*prgRowStatus = spTempRowStatus;
			}

			if (*prgRowStatus == NULL)
			{
				if(prgRows && *prgRows)
				{
					CoTaskMemFree(*prgRows);
					*prgRows = NULL;
				}
				return E_OUTOFMEMORY;
			}
		}
		else
		{
			if (prgRowStatus != NULL && bIgnore == false)
				*prgRowStatus = NULL;
		}

		// NOTE:  If cRows is zero, then we should ignore rghRows and
		// update all of the rows in the cached map.
		RowClass* pRow = NULL;
		POSITION pos = pT->m_rgRowHandles.GetStartPosition();
		for (ULONG ulRow = 0; ulRow < ulRowUpdate; ulRow++)
		{
			void* pData = NULL;
			HROW hRowUpdate = NULL;
			ULONG ulAlreadyProcessed = 0;
			bool bDupRow = false;
			LONG ulCurrentRow = ulCount;

			// Fetch the HROW, RowClass, and Data for the row.  Perform
			// Validation as necessiary
			if (cRows != 0)
			{
				hRowUpdate = rghRows[ulRow];

				for (ULONG ulCheckDup = 0; ulCheckDup < ulRow; ulCheckDup++)
				{
					if (pT->IsSameRow(hRowUpdate, rghRows[ulCheckDup]) == S_OK)
					{
						ulAlreadyProcessed = ulCheckDup;
						bDupRow = true;
						break;
					}
				}
			}
			else
			{
				ATLASSERT(ulRow < (ULONG)pT->m_rgRowHandles.GetCount());
				ATLASSERT( pos != NULL );
				typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext(pos);
				ATLASSERT( pPair != NULL );
				if(!pPair)
				{
					return E_FAIL;
				}
				hRowUpdate = pPair->m_key;
			}

			if (prgRows != NULL && bIgnore == false)
				(*prgRows)[ulCurrentRow] = hRowUpdate;


			if (bDupRow != false)
			{
				// We've already set the row before, just copy status and
				// continue processing
				if (prgRowStatus != NULL && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = (*prgRowStatus)[ulAlreadyProcessed];

				ulCount++;
				continue;
			}

			bool bFound = pT->m_rgRowHandles.Lookup(hRowUpdate, pRow);
			if (!bFound || pRow == NULL)
			{
				if (prgRowStatus != NULL  && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_INVALID;
				bFailed = true;
				ulCount++;
				continue;
			}

			// If the row is not UNCHANGED, send preliminary notifications before beginning processing
			if( pRow->m_status != 0 &&
				pRow->m_status != DBPENDINGSTATUS_UNCHANGED )
			{
				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{

					HRESULT hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUpdate, DBREASON_ROW_UPDATE,
															DBEVENTPHASE_OKTODO, FALSE);
					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
					{
						if (prgRowStatus != NULL && bIgnore == false)
							(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
						bFailed = true;
						continue;
					}

					hrNotify = pT->Fire_OnRowChange(pT, 1, &hRowUpdate, DBREASON_ROW_UPDATE,
															DBEVENTPHASE_ABOUTTODO, FALSE);
					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
					{
						if (prgRowStatus != NULL && bIgnore == false)
							(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
						bFailed = true;
						continue;
					}

					hrNotify = pT->Fire_OnRowChange(pT, 1, (HROW*)&hRowUpdate, DBREASON_ROW_UPDATE,
															DBEVENTPHASE_SYNCHAFTER, FALSE);

					if ((hrNotify != S_OK) && (hrNotify != E_FAIL))
					{
						if (prgRowStatus != NULL && bIgnore == false)
							(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_CANCELED;
						bFailed = true;
						continue;
					}
				}
			}

			// If cRows is zero we'll go through all rows fetched.  We
			// shouldn't increment the attempted count for rows that are
			// not changed
			if (cRows != 0 || (pRow != NULL &&
				pRow->m_status !=0 && pRow->m_status != DBPENDINGSTATUS_UNCHANGED
				&& pRow->m_status != DBPENDINGSTATUS_INVALIDROW))
				ulCount++;
			else
				continue;


			switch(pRow->m_status)
			{
			case DBPENDINGSTATUS_INVALIDROW:		// Row is bad or deleted
				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					pT->Fire_OnRowChange(pT, 1, (HROW*)&hRowUpdate, DBREASON_ROW_UPDATE,
						DBEVENTPHASE_FAILEDTODO, TRUE);
				}
				if (prgRowStatus != NULL && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_DELETED;
				bFailed = true;
				continue;
				break;

			case DBPENDINGSTATUS_UNCHANGED:
			case 0:
				// If the row's status is not changed, then just put S_OK
				// and continue.  The spec says we should not transmit the
				// request to the data source (as nothing would change).
				//_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				//{
				//	pT->Fire_OnRowChange(pT, 1, (HROW*)&hRowUpdate, DBREASON_ROW_UPDATE,
				//		DBEVENTPHASE_DIDEVENT, TRUE);
				//}
				if (prgRowStatus != NULL && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_S_OK;
				bSucceeded = true;
				continue;
				break;

			default:
				pData = &pT->m_rgRowData[pRow->m_iRowset];

				if (pData == NULL)
				{
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						pT->Fire_OnRowChange(pT, 1, (HROW*)&hRowUpdate, DBREASON_ROW_UPDATE,
							DBEVENTPHASE_FAILEDTODO, TRUE);
					}
					if (prgRowStatus != NULL && bIgnore == false)
						(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_INVALID;
					bFailed = true;
					continue;
				}

				// If the rghRows variable is NULL, we now add the row to
				// prgRows.  We do this because we have determined that the
				// row has changed. AddRef the row as it may not be held by
				// the consumer.
				if (cRows == 0)
					pRow->AddRefRow();

				// Ask the provider if it is OK to change this row.
				DBROWSTATUS status = DBROWSTATUS_S_OK;
				if (FAILED(IsUpdateAllowed(pRow->m_status, hRowUpdate, &status)))
				{
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						pT->Fire_OnRowChange(pT, 1, (HROW*)&hRowUpdate, DBREASON_ROW_UPDATE,
							DBEVENTPHASE_FAILEDTODO, TRUE);
					}
					if (prgRowStatus != NULL && bIgnore == false)
						(*prgRowStatus)[ulCurrentRow] = status;
					bFailed = true;
					continue;
				}

				if (pRow->m_status == DBPENDINGSTATUS_DELETED)
				{
					pT->m_rgRowData.RemoveAt(pRow->m_iRowset);
					// Need to update any outstanding pRow->m_iRowset
					// variables
					POSITION posRow = pT->m_rgRowHandles.GetStartPosition();
					while( posRow != NULL )
					{
						typename MapClass::CPair* pPair = pT->m_rgRowHandles.GetNext(posRow);
						ATLASSERT( pPair != NULL );
						RowClass* pCheckRow = pPair->m_value;
						if (pCheckRow != NULL &&
							pCheckRow->m_iRowset > pRow->m_iRowset)
							pCheckRow->m_iRowset--;
					}

					// Per the specification, deleted rows transmitted
					// to the data source are set to
					// DBPENDINGSTATUS_INVALIDROW
					pRow->m_status = DBPENDINGSTATUS_INVALIDROW;
				}

				// Copy data back
				if (FAILED(pT->FlushData(hRowUpdate, NULL)))
				{
					if (prgRowStatus != NULL && bIgnore == false)
						(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_E_FAIL;
					_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
					{
						pT->Fire_OnRowChange(pT, 1, (HROW*)&hRowUpdate, DBREASON_ROW_UPDATE,
							DBEVENTPHASE_FAILEDTODO, TRUE);
					}
					bFailed = true;
					continue;
				}

				if (prgRowStatus != NULL && bIgnore == false)
					(*prgRowStatus)[ulCurrentRow] = DBROWSTATUS_S_OK;
				bSucceeded = true;

				if (pRow->m_status != DBPENDINGSTATUS_DELETED)
				{
					if (pRow->m_status != DBPENDINGSTATUS_INVALIDROW)
						pRow->m_status = DBPENDINGSTATUS_UNCHANGED;
				}

				// Remove undo buffer.
				if (m_mapCachedData.Lookup(hRowUpdate) != NULL)
					m_mapCachedData.RemoveKey(hRowUpdate);

				// Check if we need to release the row because it's ref was 0
				// See the IRowset::ReleaseRows section in the spec for more
				// information
				if (pRow->m_dwRef == 0)
				{
					pRow->AddRefRow();	// Artifically bump this to remove it
					if( FAILED( pT->RefRows(1, &hRowUpdate, NULL, NULL, false) ) )
						return E_FAIL;
				}

				_ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
				{
					pT->Fire_OnRowChange(pT, 1, (HROW*)&hRowUpdate, DBREASON_ROW_UPDATE,
						DBEVENTPHASE_DIDEVENT, TRUE);
				}

				break;

			}
		}

		// Set the output for rows undone.
		if (pcRows)
		{
			if( cRows==0 )
				*pcRows = ulCount;
			else
				*pcRows = cRows;
		}

		// In the case where cRows == 0, we need to allocate the final
		// array of HROWs and status values.
		if (cRows == 0)
		{
			HROW* prowTemp;
			DBROWSTATUS* prowStatus;

			if (prgRows != NULL && ulCount != 0 && bIgnore == false)
			{
				prowTemp = (HROW*)::ATL::AtlCoTaskMemCAlloc(ulCount, static_cast<ULONG>(sizeof(HROW)));
				if (prowTemp == NULL)
				{
					if (cRows != 0 && prgRows != NULL)
						CoTaskMemFree(*prgRows);

					if (cRows != 0 && prgRowStatus != NULL)
						CoTaskMemFree(*prgRowStatus);

					return E_OUTOFMEMORY;
				}

				Checked::memcpy_s(prowTemp, sizeof(HROW) * ulCount, *prgRows, sizeof(HROW) * ulCount);
				if (cRows != 0)
					CoTaskMemFree(*prgRows);
				*prgRows = prowTemp;
			}
			else
			{
				if (prgRows != NULL && bIgnore == false)
					*prgRows = NULL;
			}

			if (prgRowStatus != NULL && ulCount != 0 && bIgnore == false)
			{
				prowStatus = (DBROWSTATUS*)::ATL::AtlCoTaskMemCAlloc(ulCount, static_cast<ULONG>(sizeof(DBROWSTATUS)));
				if (prowStatus == NULL)
				{
					if (cRows != 0 && prgRows != NULL)
						CoTaskMemFree(*prgRows);

					if (cRows != 0 && prgRowStatus != NULL)
						CoTaskMemFree(*prgRowStatus);

					return E_OUTOFMEMORY;
				}

				Checked::memcpy_s(prowStatus, sizeof(DBROWSTATUS)*ulCount, *prgRowStatus, sizeof(DBROWSTATUS)*ulCount);
				if (cRows != 0)
					CoTaskMemFree(*prgRowStatus);
				*prgRowStatus = prowStatus;
			}
			else
			{
				if (prgRowStatus != NULL && bIgnore == false)
					*prgRowStatus = NULL;
			}
		}

		// Send the return value
		if (!bFailed)
			return S_OK;
		else
		{
			if (!bSucceeded)
				return DB_E_ERRORSOCCURRED;
			else
				return DB_S_ERRORSOCCURRED;
		}
	}

	// Callback functions
	HRESULT IsUpdateAllowed(
		_In_ DBPENDINGSTATUS /*status*/,
		_In_ HROW /*hRowUpdate*/,
		_In_opt_ DBROWSTATUS* /*pRowStatus*/)
	{
		// If you need to perform any checks for security, integrity
		// violations, etc. as a result of an update, override this
		// method.

		// Make sure you put the appropriate status in the pRowStatus
		// as it will be returned to the user.
		return S_OK;
	}

	// Map containing original data for deferred operation
	CAtlMap<HROW, Storage*> m_mapCachedData;
};


#undef _ATL_IF_EXISTS_FIRE_ON_FIELD_CHANGE
#undef _ATL_IF_EXISTS_FIRE_ON_ROW_CHANGE
#undef _ATL_IF_EXISTS_FIRE_ON_ROWSET_CHANGE

}; //namespace ATL

#pragma pack(pop)
#pragma warning(pop)

#endif //__ATLDB_H__
